crud voucher import listing done

dev
Aji Kamaludin 1 year ago
parent 5a3bfa0be1
commit 90639050f9
No known key found for this signature in database
GPG Key ID: 19058F67F0083AD3

@ -58,3 +58,21 @@ VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
HTTPS_AWARE=false
DS_APP_HOST=10.25.10.1
DS_APP_PORT=9191
DS_SEND_QUERIES=false
DS_SEND_HTTP_CLIENT_REQUESTS=false
DS_SEND_JOBS=false
DS_SEND_COMMANDS=false
DS_SEND_SCHEDULED_COMMANDS=false
DS_SEND_CACHE=false
DS_SEND_GATE=false
DS_SEND_LOGS=false
DS_SEND_LIVEWIRE_COMPONENTS=false
DS_LIVEWIRE_EVENTS=false
DS_LIVEWIRE_DISPATCH=false
DS_SEND_LIVEWIRE_FAILED_VALIDATION=false
DS_AUTO_CLEAR_ON_PAGE_RELOAD=false
DS_AUTO_INVOKE_APP=false
DS_PREFERRED_IDE=vscode

@ -18,6 +18,6 @@ class CustomerController extends Controller
->orWhere('username', 'like', "%$request->q%");
}
return $query->get();
return $query->limit(100)->get();
}
}

@ -16,6 +16,6 @@ class LocationController extends Controller
$query->where('name', 'like', "%$request->q%");
}
return $query->get();
return $query->limit(100)->get();
}
}

@ -0,0 +1,23 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\LocationProfile;
use Illuminate\Http\Request;
class LocationProfileController extends Controller
{
public function index(Request $request)
{
$query = LocationProfile::with(['location'])->orderBy('updated_at', 'desc');
if ($request->q != '') {
$query->where('name', 'like', "%$request->q%")
->orWhere('display_note', 'like', "%$request->q%")
->orWhereHas('location', fn ($q) => $q->where('name', 'like', "%$request->q%"));
}
return $query->limit(100)->get();
}
}

@ -16,6 +16,6 @@ class RoleController extends Controller
$query->where('name', 'like', "%{$request->q}%");
}
return $query->get();
return $query->limit(100)->get();
}
}

@ -7,10 +7,14 @@ use Illuminate\Http\Request;
class LocationController extends Controller
{
public function index()
public function index(Request $request)
{
$query = Location::orderBy('updated_at', 'desc');
if ($request->q != '') {
$query->where('name', 'like', "%$request->q%");
}
return inertia('Location/Index', [
'query' => $query->paginate(),
]);

@ -97,7 +97,7 @@ class LocationProfileController extends Controller
return inertia('LocationProfile/Form', [
'expireds' => LocationProfile::EXPIRED_UNIT,
'levels' => CustomerLevel::all(),
'profile' => $profile->load(['location', 'prices.level'])
'profile' => $profile->load(['location', 'prices.level']),
]);
}

@ -3,6 +3,8 @@
namespace App\Http\Controllers;
use App\Models\CustomerLevel;
use App\Models\Location;
use App\Models\LocationProfile;
use App\Models\Voucher;
use App\Services\GeneralService;
use Illuminate\Http\Request;
@ -11,22 +13,57 @@ use Illuminate\Support\Str;
class VoucherController extends Controller
{
public function index(Request $request)
public function location(Request $request)
{
$query = Voucher::with(['location'])->orderBy('updated_at', 'desc');
$query = Location::orderBy('updated_at', 'desc');
if ($request->q != '') {
$query->where('username', 'like', "%$request->q%")
->orWhere('comment', 'like', "%$request->q%")
->orWhere('profile', 'like', "%$request->q%");
$query->where('name', 'like', "%$request->q%");
}
return inertia('Voucher/Location', [
'query' => tap($query->paginate(20))->append(['count_vouchers']),
]);
}
public function profile(Request $request, Location $location)
{
$query = LocationProfile::where('location_id', $location->id)
->orderBy('updated_at', 'desc');
if ($request->location_id != '') {
$query->where('location_id', $request->location_id);
}
if ($request->q != '') {
$query->where('name', 'like', "%$request->q%")
->orWhere('display_note', 'like', "%$request->q%");
}
return inertia('Voucher/Profile', [
'query' => tap($query->paginate(20))->append(['count_vouchers']),
'location' => $location,
'stats' => Voucher::stats($location),
]);
}
public function index(Request $request, Location $location, LocationProfile $profile)
{
$query = Voucher::with(['profile.location'])
->where('location_profile_id', $profile->id)
->orderBy('updated_at', 'desc');
if ($request->q != '') {
$query->where('username', 'like', "%$request->q%")
->orWhere('comment', 'like', "%$request->q%")
->orWhere('profile', 'like', "%$request->q%");
}
return inertia('Voucher/Index', [
'query' => $query->paginate(),
'location' => $location,
'profile' => $profile,
'stats' => Voucher::stats($location),
]);
}
@ -40,55 +77,22 @@ class VoucherController extends Controller
public function store(Request $request)
{
$request->validate([
'name' => 'nullable|string',
'description' => 'nullable|string',
'location_id' => 'required|exists:locations,id',
'location_profile_id' => 'required|exists:location_profiles,id',
'username' => 'required|string',
'password' => 'required|string',
'discount' => 'required|numeric',
'display_price' => 'required|numeric',
'price_poin' => 'nullable|numeric',
'quota' => 'required|string',
'profile' => 'required|string',
'comment' => 'required|string',
'expired' => 'required|numeric',
'expired_unit' => 'required|string',
'prices' => 'nullable|array',
'prices.*.customer_level_id' => 'required|exists:customer_levels,id',
'prices.*.display_price' => 'required|numeric',
]);
DB::beginTransaction();
$voucher = Voucher::create([
'name' => $request->name,
'description' => $request->description,
'location_id' => $request->location_id,
Voucher::create([
'location_profile_id' => $request->location_profile_id,
'username' => $request->username,
'password' => $request->password,
'discount' => $request->discount,
'display_price' => $request->display_price,
'price_poin' => $request->price_poin,
'quota' => $request->quota,
'profile' => $request->profile,
'comment' => $request->comment,
'expired' => $request->expired,
'expired_unit' => $request->expired_unit,
'password' => $request->username,
]);
foreach (collect($request->prices) as $price) {
$finalPrice = $price['display_price'];
if ($voucher->discount > 0) {
$finalPrice = $finalPrice - round($finalPrice * ($voucher->discount / 100), 2);
}
$voucher->prices()->create([
'customer_level_id' => $price['customer_level_id'],
'price' => $finalPrice,
'display_price' => $price['display_price'],
]);
}
DB::commit();
$profile = LocationProfile::find($request->location_profile_id);
return redirect()->route('voucher.index')
return redirect()->route('voucher.index', [
'location' => $profile->location_id,
'profile' => $profile->id
])
->with('message', ['type' => 'success', 'message' => 'Item has beed saved']);
}
@ -103,62 +107,22 @@ class VoucherController extends Controller
public function update(Request $request, Voucher $voucher)
{
$request->validate([
'name' => 'nullable|string',
'description' => 'nullable|string',
'location_id' => 'required|exists:locations,id',
'location_profile_id' => 'required|exists:location_profiles,id',
'username' => 'required|string',
'password' => 'required|string',
'discount' => 'required|numeric',
'display_price' => 'required|numeric',
'price_poin' => 'nullable|numeric',
'quota' => 'required|string',
'profile' => 'required|string',
'comment' => 'required|string',
'expired' => 'required|numeric',
'expired_unit' => 'required|string',
'prices' => 'nullable|array',
'prices.*.customer_level_id' => 'required|exists:customer_levels,id',
'prices.*.display_price' => 'required|numeric',
]);
DB::beginTransaction();
$voucher->update([
'location_profile_id' => $request->location_profile_id,
'username' => $request->username,
'password' => $request->password,
'password' => $request->username,
]);
$vouchers = Voucher::where('batch_id', $voucher->batch_id);
$vouchers->update([
'name' => $request->name,
'description' => $request->description,
'location_id' => $request->location_id,
'discount' => $request->discount,
'display_price' => $request->display_price,
'price_poin' => $request->price_poin,
'quota' => $request->quota,
'profile' => $request->profile,
'comment' => $request->comment,
'expired' => $request->expired,
]);
$profile = LocationProfile::find($request->location_profile_id);
foreach ($vouchers->get() as $voucher) {
$voucher->prices()->delete();
foreach (collect($request->prices) as $price) {
$finalPrice = $price['display_price'];
if ($voucher->discount > 0) {
$finalPrice = $finalPrice - round($finalPrice * ($voucher->discount / 100), 2);
}
$voucher->prices()->create([
'customer_level_id' => $price['customer_level_id'],
'price' => $finalPrice,
'display_price' => $price['display_price'],
]);
}
}
DB::commit();
return redirect()->route('voucher.index')
return redirect()->route('voucher.index', [
'location' => $profile->location_id,
'profile' => $profile->id
])
->with('message', ['type' => 'success', 'message' => 'Item has beed updated']);
}
@ -166,67 +130,55 @@ class VoucherController extends Controller
{
$voucher->delete();
return redirect()->route('voucher.index')
return redirect()->route('voucher.index', [
'location' => $voucher->profile->location_id,
'profile' => $voucher->profile->id
])
->with('message', ['type' => 'success', 'message' => 'Item has beed deleted']);
}
public function form_import()
{
return inertia('Voucher/Import', [
'levels' => CustomerLevel::all(),
]);
return inertia('Voucher/Import');
}
public function import(Request $request)
{
$request->validate([
'script' => 'required|string',
'location_id' => 'required|exists:locations,id',
'discount' => 'required|numeric',
'display_price' => 'required|numeric',
'price_poin' => 'nullable|numeric',
'expired' => 'required|numeric',
'expired_unit' => 'required|string',
'prices' => 'nullable|array',
'prices.*.customer_level_id' => 'required|exists:customer_levels,id',
'prices.*.display_price' => 'required|numeric',
'location_profile_id' => 'required|exists:location_profiles,id',
]);
$profile = LocationProfile::find($request->location_profile_id);
$batchId = Str::ulid();
$vouchers = GeneralService::script_parser($request->script);
if (count($vouchers) <= 0) {
return redirect()->route('voucher.index', [
'location' => $profile->location_id,
'profile' => $profile->id
])
->with('message', ['type' => 'error', 'message' => 'Nothing to import']);
}
DB::beginTransaction();
foreach ($vouchers as $voucher) {
$voucher = Voucher::create([
'location_id' => $request->location_id,
'location_profile_id' => $request->location_profile_id,
'username' => $voucher['username'],
'password' => $voucher['password'],
'discount' => $request->discount,
'display_price' => $request->display_price,
'price_poin' => $request->price_poin,
'quota' => $voucher['quota'],
'profile' => $voucher['profile'],
'comment' => $voucher['comment'],
'expired' => $request->expired,
'expired_unit' => $request->expired_unit,
'batch_id' => $batchId,
]);
foreach (collect($request->prices) as $price) {
$finalPrice = $price['display_price'];
if ($voucher->discount > 0) {
$finalPrice = $finalPrice - round($finalPrice * ($voucher->discount / 100), 2);
}
$voucher->prices()->create([
'customer_level_id' => $price['customer_level_id'],
'price' => $finalPrice,
'display_price' => $price['display_price'],
]);
}
}
DB::commit();
return redirect()->route('voucher.index')
return redirect()->route('voucher.index', [
'location' => $profile->location_id,
'profile' => $profile->id
])
->with('message', ['type' => 'success', 'message' => 'Items has beed saved']);
}
}

@ -9,5 +9,7 @@ class Account extends Model
'bank_name',
'holder_name',
'account_number',
'logo',
'admin_fee',
];
}

@ -2,6 +2,8 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
class Location extends Model
{
protected $fillable = [
@ -9,4 +11,26 @@ class Location extends Model
'description',
'logo',
];
public function profiles()
{
return $this->hasMany(LocationProfile::class);
}
public function countVouchers(): Attribute
{
return Attribute::make(get: function () {
$profiles = $this->profiles()->select('id', 'min_stock')->get();
$minStock = $profiles->min('min_stock');
$profileIds = $profiles->pluck('id')->toArray();
$unsoldCount = Voucher::whereIn('location_profile_id', $profileIds)
->where('is_sold', Voucher::UNSOLD)
->count();
return [
'color' => $unsoldCount <= $minStock ? 'bg-red-200 border-red-500' : 'bg-green-200 border-green-500',
'unsold_count' => $unsoldCount,
];
});
}
}

@ -70,10 +70,27 @@ class LocationProfile extends Model
return $this->belongsTo(Location::class);
}
public function vouchers()
{
return $this->hasMany(Voucher::class);
}
public function diplayExpired(): Attribute
{
return Attribute::make(get: function () {
return $this->expired . ' ' . $this->expired_unit;
return $this->expired.' '.$this->expired_unit;
});
}
public function countVouchers(): Attribute
{
return Attribute::make(get: function () {
$unsoldCount = $this->vouchers()->where('is_sold', Voucher::UNSOLD)->count();
return [
'color' => $unsoldCount <= $this->min_stock ? 'bg-red-200 border-red-500' : 'bg-green-200 border-green-500',
'unsold_count' => $unsoldCount,
];
});
}
}

@ -15,7 +15,7 @@ class Sale extends Model
const PAYED_WITH_PAYLATER = 'paylater';
const PAYED_WITH_poin = 'poin';
const PAYED_WITH_POIN = 'poin';
protected $fillable = [
'code',
@ -69,7 +69,7 @@ class Sale extends Model
public function create_notification()
{
if ($this->payed_with == self::PAYED_WITH_poin) {
if ($this->payed_with == self::PAYED_WITH_POIN) {
Notification::create([
'entity_type' => User::class,
'description' => $this->customer->fullname.' melakukan penukaran '.$this->items()->count().' voucher sebesar '.$this->items->value('price').' poin',

@ -2,6 +2,8 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
class Voucher extends Model
{
const UNSOLD = 0;
@ -20,7 +22,48 @@ class Voucher extends Model
'is_sold', //menandakan sudah terjual atau belum
];
protected $appends = ['display_quota', 'display_expired', 'validate_price', 'validate_display_price'];
protected $appends = [
'validate_price',
'validate_display_price',
'status',
'created_at_formated'
];
public function profile()
{
return $this->belongsTo(LocationProfile::class, 'location_profile_id');
}
public function validatePrice(): Attribute
{
return Attribute::make(get: function () {
return '';
});
}
public function validateDisplayPrice(): Attribute
{
return Attribute::make(get: function () {
return '';
});
}
public function status(): Attribute
{
return Attribute::make(get: function () {
return [
'color' => $this->sold == self::SOLD ? 'bg-green-200 border-green-600' : 'bg-yellow-100 border-yellow-300',
'text' => $this->sold == self::SOLD ? 'Ya' : 'Tidak'
];
});
}
public function createdAtFormated(): Attribute
{
return Attribute::make(get: function () {
return $this->created_at->format('d/m/Y H:i:s');
});
}
public function shuffle_unsold()
{
@ -54,8 +97,45 @@ class Voucher extends Model
if ($count <= $treshold) {
Notification::create([
'entity_type' => User::class,
'description' => 'stok voucher '.$this->location->name.' ( '.$this->profile.' ) '.'tersisa : '.$count,
'description' => 'stok voucher ' . $this->location->name . ' ( ' . $this->profile . ' ) ' . 'tersisa : ' . $count,
]);
}
}
public static function stats(Location $location)
{
$profileIds = LocationProfile::where('location_id', $location->id)->pluck('id')->toArray();
$count_voucher_total = Voucher::whereIn('location_profile_id', $profileIds)->count();
$sum_voucher_total = Voucher::whereIn('location_profile_id', $profileIds)
->join('location_profiles', 'location_profiles.id', '=', 'vouchers.location_profile_id')
->selectRaw('(count(vouchers.id) * location_profiles.price) as total')
->value('total');
$count_voucher_sold = Voucher::whereIn('location_profile_id', $profileIds)->where('is_sold', Voucher::SOLD)->count();
$count_voucher_unsold = Voucher::whereIn('location_profile_id', $profileIds)->where('is_sold', Voucher::UNSOLD)->count();
$sum_voucher_unsold = Voucher::whereIn('location_profile_id', $profileIds)
->where('is_sold', Voucher::UNSOLD)
->join('location_profiles', 'location_profiles.id', '=', 'vouchers.location_profile_id')
->selectRaw('(count(vouchers.id) * location_profiles.price) as total')
->value('total');
$voucherIds = Voucher::whereIn('location_profile_id', $profileIds)->pluck('id')->toArray();
$sum_voucher_sold = SaleItem::whereIn('entity_id', $voucherIds)
->whereHas('sale', function ($q) {
$q->where('payed_with', Sale::PAYED_WITH_DEPOSIT)
->orWhere('payed_with', Sale::PAYED_WITH_PAYLATER);
})
->selectRaw('SUM(price * quantity) as total')
->value('total');
return [
'count_voucher_total' => $count_voucher_total,
'sum_voucher_total' => $sum_voucher_total,
'count_voucher_sold' => $count_voucher_sold,
'sum_voucher_sold' => $sum_voucher_sold,
'count_voucher_unsold' => $count_voucher_unsold,
'sum_voucher_unsold' => $sum_voucher_unsold,
];
}
}

@ -21,7 +21,7 @@
"require-dev": {
"beyondcode/laravel-dump-server": "^1.9",
"fakerphp/faker": "^1.23.0",
"laradumps/laradumps": "^1.12.3",
"laradumps/laradumps": "^1.12",
"laravel/breeze": "^1.21.0",
"laravel/pint": "^1.10.2",
"laravel/sail": "^1.22.0",

2
composer.lock generated

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "37cb54e2418cfe92a600a5757cb08191",
"content-hash": "1dc99b21d1aa2534f1a2a2e6f28ed566",
"packages": [
{
"name": "brick/math",

@ -12,6 +12,7 @@ use App\Models\Voucher;
use App\Services\GeneralService;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class DummySeeder extends Seeder
{
@ -43,8 +44,8 @@ class DummySeeder extends Seeder
$images = ['1.webp', '2.webp', '3.webp'];
foreach ($images as $index => $image) {
Banner::create([
'title' => 'Banner ' . $index,
'image' => 'sample/' . $image,
'title' => 'Banner '.$index,
'image' => 'sample/'.$image,
'description' => '<h1>Banner </h1>',
]);
}
@ -69,11 +70,13 @@ class DummySeeder extends Seeder
public function location()
{
$locations = ['Jarbriel.id', 'Shaff.net', 'Weslycamp.net', 'Glory.net', 'Salgo.id', 'Terna.id', 'Kanza.id'];
$locations = [
'jabriel.id', 'shaff.net', 'weslycamp.net', 'glory.net', 'agelos.net', 'brigton.ne', 'tairon.net', 'jeconia.net', 'metsawifi.net', 'donata.net', 'hoptar.id', 'salgo.id', 'feivel.id', 'carenet.id', 'ivena.id', 'dishan.id', 'imago.id', 'netif.id', 'gavi.id', 'terna.id', 'kanza.id', 'benaya.id', 'rega.id', 'ponix.id', 'drago.id', 'lexsa.id', 'kilia.id', 'gramanta.id', 'vermil.id', 'nohea.id', 'ducan.id', 'letra.id', 'lejau.id', 'jelivan', 'takahiro.id', 'katsu', 'zergan', 'satoshi',
];
foreach ($locations as $location) {
Location::create([
'name' => $location,
'name' => Str::ucfirst($location),
'description' => '-',
]);
}
@ -99,7 +102,7 @@ class DummySeeder extends Seeder
$lp = LocationProfile::create([
'location_id' => $location->id,
'name' => $quota,
'name' => 'Profile '.$quota,
'quota' => $quota,
'display_note' => 'bisa semua',
'expired' => rand(1, 3),
@ -145,8 +148,7 @@ class DummySeeder extends Seeder
$vouchers = GeneralService::script_parser(file_get_contents(public_path('example.md')));
DB::beginTransaction();
foreach ([1, 2, 3] as $loop) {
$profile = LocationProfile::get()[$loop];
foreach (LocationProfile::limit(9)->get() as $profile) {
foreach ($vouchers as $voucher) {
Voucher::create([
'location_profile_id' => $profile->id,

@ -3,7 +3,9 @@ import { HiOutlineChevronLeft, HiOutlineChevronRight } from 'react-icons/hi'
import qs from 'qs'
const PageLink = ({ active, label, url, params }) => {
const className = active ? 'z-10 px-3 py-2 leading-tight text-blue-600 border border-blue-300 bg-blue-50 hover:bg-blue-100 hover:text-blue-700 dark:border-gray-700 dark:bg-gray-700 dark:text-white' : 'px-3 py-2 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white'
const className = active
? 'z-10 px-3 py-2 leading-tight text-blue-600 border border-blue-300 bg-blue-50 hover:bg-blue-100 hover:text-blue-700 dark:border-gray-700 dark:bg-gray-700 dark:text-white'
: 'px-3 py-2 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white'
const onClick = () => {
router.get(
@ -24,7 +26,7 @@ const PageLink = ({ active, label, url, params }) => {
className="block py-2 px-1 ml-0 leading-tight text-gray-500 bg-white border border-gray-300 rounded-l-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
aria-label="Previous"
>
<HiOutlineChevronLeft className='w-5 h-5'/>
<HiOutlineChevronLeft className="w-5 h-5" />
</button>
</li>
)
@ -34,8 +36,9 @@ const PageLink = ({ active, label, url, params }) => {
<li>
<button
onClick={onClick}
className="block py-2 px-1 leading-tight text-gray-500 bg-white border border-gray-300 rounded-r-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">
<HiOutlineChevronRight className='w-5 h-5'/>
className="block py-2 px-1 leading-tight text-gray-500 bg-white border border-gray-300 rounded-r-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
>
<HiOutlineChevronRight className="w-5 h-5" />
</button>
</li>
)
@ -44,7 +47,7 @@ const PageLink = ({ active, label, url, params }) => {
return (
<li>
<button className={className} onClick={onClick}>
{ label }
{label}
</button>
</li>
)
@ -62,7 +65,7 @@ const PageInactive = ({ label }) => {
disabled={true}
aria-label="Previous"
>
<HiOutlineChevronLeft className='w-5 h-5 dark:text-gray-700 text-gray-300'/>
<HiOutlineChevronLeft className="w-5 h-5 dark:text-gray-700 text-gray-300" />
</button>
</li>
)
@ -71,11 +74,11 @@ const PageInactive = ({ label }) => {
return (
<li>
<button
className="block py-2 px-1 leading-tight text-gray-500 bg-white border rounded-r-lg dark:bg-gray-800 border-gray-100 dark:text-gray-400 dark:border-gray-700"
className="block py-2 px-1 leading-tight text-gray-500 bg-white border rounded-r-lg dark:bg-gray-800 border-gray-100 dark:text-gray-400 dark:border-gray-700"
disabled={true}
aria-label="Next"
>
<HiOutlineChevronRight className='w-5 h-5 dark:text-gray-700 text-gray-300'/>
<HiOutlineChevronRight className="w-5 h-5 dark:text-gray-700 text-gray-300" />
</button>
</li>
)
@ -111,4 +114,4 @@ export default ({ links = [], params = null }) => {
</ul>
</nav>
)
}
}

@ -1,20 +1,20 @@
import React from "react";
import { HiSearch } from "react-icons/hi";
import React from 'react'
import { HiSearch } from 'react-icons/hi'
export default function SearchInput({ onChange, value }) {
return (
<div className="relative w-full">
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<HiSearch className='text-base dark:text-white'/>
<HiSearch className="text-base dark:text-white" />
</div>
<input
type="text"
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 pl-10 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"
placeholder="Search"
<input
type="text"
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 pl-10 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"
placeholder="Search"
onChange={onChange}
value={value}
autoComplete="off"
/>
</div>
)
}
}

@ -10,14 +10,13 @@ import SidebarNav from './Partials/SidebarNav'
import { HiOutlineBell } from 'react-icons/hi2'
export default function Authenticated({
auth,
children,
flash,
page = '',
action = '',
parent = null,
}) {
const {
props: { count_unread_notifications, notifications },
props: { count_unread_notifications, notifications, auth, flash },
} = usePage()
const [showingNavigationDropdown, setShowingNavigationDropdown] =
useState(false)
@ -162,16 +161,26 @@ export default function Authenticated({
</div>
<main className="w-full">
{page !== '' && (
<Breadcrumb className="bg-gray-200 py-3 px-5 mb-2 dark:bg-gray-700">
<Breadcrumb.Item
onClick={() => router.visit(route('dashboard'))}
icon={HiHome}
>
<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>
{action !== '' && (
{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>

@ -1,21 +1,55 @@
import React from 'react'
import { router, usePage } from '@inertiajs/react'
import { Link, router } from '@inertiajs/react'
import { Sidebar } from 'flowbite-react'
import { HiLogout } from 'react-icons/hi'
import { filterOpenMenu } from './helpers'
import routes from './routes'
export default function SidebarNav({ user }) {
const {
props: { app_name },
} = usePage()
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',
})
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'
}`
return (
<Link href={item.route} className={className}>
<Icon />
<span className="ml-3">{children}</span>
</Link>
)
}
const SubItem = ({ 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',
})
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'
}`
return (
<Link href={item.route} className={className}>
<Icon />
<span className="ml-3">{children}</span>
</Link>
)
}
export default function SidebarNav({ user }) {
const menus = routes.filter((item) => {
item.open = false
if (!item.show) {
return null
}
if (+user.is_superadmin === 1) {
if (user.role === null) {
return filterOpenMenu(user, item)
}
if (user.role.permissions.find((p) => p.name === item.permission)) {
@ -32,13 +66,7 @@ export default function SidebarNav({ user }) {
{menus.map((item) => (
<div key={item.name}>
{item.items === undefined ? (
<Sidebar.Item
onClick={() => router.visit(item.route)}
icon={item.icon}
active={route().current(item.active)}
>
{item.name}
</Sidebar.Item>
<Item item={item}>{item.name}</Item>
) : (
<Sidebar.Collapse
icon={item.icon}
@ -46,18 +74,9 @@ export default function SidebarNav({ user }) {
open={item.open}
>
{item.items.map((item) => (
<Sidebar.Item
key={item.name}
onClick={() =>
router.visit(item.route)
}
icon={item.icon}
active={route().current(
item.active
)}
>
<SubItem item={item} key={item.name}>
{item.name}
</Sidebar.Item>
</SubItem>
))}
</Sidebar.Collapse>
)}
@ -71,7 +90,7 @@ export default function SidebarNav({ user }) {
</Sidebar.Item>
</Sidebar.ItemGroup>
<p className="text-sm font-light text-gray-900 dark:text-gray-100 text-center bottom-4 left-4 pt-10">
{app_name} &copy; {new Date().getFullYear()}
Elsoft &copy; {new Date().getFullYear()}
</p>
</Sidebar.Items>
</Sidebar>

@ -1,21 +1,25 @@
export const filterOpenMenu = (user, item) => {
const isAdmin = +user.is_superadmin === 1
const isAdmin = user.role === null
if ('items' in item) {
let items = []
if (isAdmin) {
items = item.items
} else {
items = item.items.filter(item => user.role.permissions.find(p => p.name === item.permission) ? item : null)
items = item.items.filter((item) =>
user.role.permissions.find((p) => p.name === item.permission)
? item
: null
)
}
if (items.length > 0) {
let activeItem = items.map(item => route().current(item.active))
let activeItem = items.map((item) => route().current(item.active))
item.open = activeItem.includes(true)
item.items = items.filter(item => item.show ? item : null)
item.items = items.filter((item) => (item.show ? item : null))
return item
}
}
if (isAdmin) {
return item
}
}
}

@ -16,6 +16,7 @@ import {
HiOutlineCurrencyDollar,
HiOutlineGlobeAlt,
HiQuestionMarkCircle,
HiShoppingCart,
HiTicket,
HiUserCircle,
} from 'react-icons/hi2'
@ -49,14 +50,14 @@ export default [
name: 'Voucher',
show: true,
icon: HiTicket,
route: route('voucher.index'),
route: route('voucher.location'),
active: 'voucher.*',
permission: 'view-voucher',
},
{
name: 'Sale',
show: true,
icon: HiCreditCard,
icon: HiShoppingCart,
route: route('sale.index'),
active: 'sale.*',
permission: 'view-sale',
@ -133,12 +134,12 @@ export default [
],
},
{
name: 'User',
name: 'Admin',
show: true,
icon: HiUser,
items: [
{
name: 'Roles',
name: 'Rule Admin',
show: true,
icon: HiUserGroup,
route: route('roles.index'),
@ -146,7 +147,7 @@ export default [
permission: 'view-role',
},
{
name: 'Users',
name: 'List Admin',
show: true,
icon: HiUsers,
route: route('user.index'),

@ -42,13 +42,7 @@ export default function Account(props) {
const canDelete = hasPermission(auth, 'delete-account')
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Bank Akun'}
action={''}
>
<AuthenticatedLayout page={'Bank Akun'} action={''}>
<Head title="Bank Akun" />
<div>

@ -48,13 +48,7 @@ export default function Form(props) {
}, [banner])
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Banner'}
action={'Form'}
>
<AuthenticatedLayout page={'Banner'} action={'Form'}>
<Head title="Banner" />
<div>

@ -35,13 +35,7 @@ export default function Info(props) {
const canDelete = hasPermission(auth, 'delete-banner')
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Banner'}
action={''}
>
<AuthenticatedLayout page={'Banner'} action={''}>
<Head title="Banner" />
<div>

@ -220,13 +220,7 @@ const Paylater = () => {
export default function Form(props) {
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Customer'}
action={'Form'}
>
<AuthenticatedLayout page={'Customer'} action={'Form'}>
<Head title="Customer" />
<div>

@ -53,13 +53,7 @@ export default function Customer(props) {
const canDelete = hasPermission(auth, 'delete-customer')
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Customer'}
action={''}
>
<AuthenticatedLayout page={'Customer'} action={''}>
<Head title="Customer" />
<div>

@ -25,13 +25,7 @@ export default function Info(props) {
const canUpdate = hasPermission(auth, 'update-customer-level')
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Customer Level'}
action={''}
>
<AuthenticatedLayout page={'Customer Level'} action={''}>
<Head title="Customer Level" />
<div>

@ -94,13 +94,7 @@ export default function Dashboard(props) {
}, [dates, customer_id, location_id])
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Dashboard'}
action={''}
>
<AuthenticatedLayout page={'Dashboard'} action={''}>
<Head title="Dashboard" />
<div>

@ -44,13 +44,7 @@ export default function Index(props) {
const canUpdate = hasPermission(auth, 'update-deposit')
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Deposit'}
action={''}
>
<AuthenticatedLayout page={'Deposit'} action={''}>
<Head title="Deposit" />
<div>

@ -43,13 +43,7 @@ export default function Info(props) {
const canDelete = hasPermission(auth, 'delete-info')
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Info'}
action={''}
>
<AuthenticatedLayout page={'Info'} action={''}>
<Head title="Info" />
<div>

@ -19,6 +19,9 @@ export default function Index(props) {
auth,
} = props
const [search, setSearch] = useState('')
const preValue = usePrevious(`${search}`)
const confirmModal = useModalState()
const formModal = useModalState()
@ -38,24 +41,32 @@ export default function Index(props) {
}
}
const params = { q: search }
useEffect(() => {
if (preValue) {
router.get(
route(route().current()),
{ q: search },
{
replace: true,
preserveState: true,
}
)
}
}, [search])
const canCreate = hasPermission(auth, 'create-location')
const canUpdate = hasPermission(auth, 'update-location')
const canDelete = hasPermission(auth, 'delete-location')
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Location'}
action={''}
>
<AuthenticatedLayout page={'Location'} action={''}>
<Head title="Location" />
<div>
<div className="mx-auto sm:px-6 lg:px-8 ">
<div className="p-6 overflow-hidden shadow-sm sm:rounded-lg bg-gray-200 dark:bg-gray-800 space-y-4">
<div className="flex justify-between">
<div className="flex flex-col lg:flex-row gap-1 justify-between">
{canCreate && (
<Button
size="sm"
@ -64,6 +75,16 @@ export default function Index(props) {
Tambah
</Button>
)}
<div className="flex flex-col md:flex-row gap-1 items-center">
<div className="w-full max-w-md">
<SearchInput
onChange={(e) =>
setSearch(e.target.value)
}
value={search}
/>
</div>
</div>
</div>
<div className="overflow-auto">
<div>
@ -151,7 +172,7 @@ export default function Index(props) {
</table>
</div>
<div className="w-full flex items-center justify-center">
<Pagination links={links} />
<Pagination links={links} params={params} />
</div>
</div>
</div>

@ -114,13 +114,7 @@ export default function Form(props) {
}, [profile])
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Profile Lokasi'}
action={'Form'}
>
<AuthenticatedLayout page={'Profile Lokasi'} action={'Form'}>
<Head title="Profile Lokasi" />
<div>

@ -6,12 +6,12 @@ import { Button, Dropdown } from 'flowbite-react'
import { HiPencil, HiTrash } from 'react-icons/hi'
import { useModalState } from '@/hooks'
import { 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 { hasPermission } from '@/utils'
export default function Index(props) {
const {
@ -57,19 +57,13 @@ export default function Index(props) {
const canDelete = hasPermission(auth, 'delete-location-profile')
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Profile Lokasi'}
action={''}
>
<AuthenticatedLayout page={'Profile Lokasi'} action={''}>
<Head title="Profile Lokasi" />
<div>
<div className="mx-auto sm:px-6 lg:px-8 ">
<div className="p-6 overflow-hidden shadow-sm sm:rounded-lg bg-gray-200 dark:bg-gray-800 space-y-4">
<div className="flex justify-between">
<div className="flex flex-col lg:flex-row gap-1 justify-between">
{canCreate && (
<div className="flex flex-row space-x-2">
<Link
@ -77,25 +71,24 @@ export default function Index(props) {
>
<Button size="sm">Tambah</Button>
</Link>
{/* <Link
href={route('location-profile.import')}
>
<Button size="sm" outline>
Import
</Button>
</Link> */}
</div>
)}
<div className="flex flex-row space-x-2 items-center">
<LocationSelectionInput
itemSelected={location}
onItemSelected={(id) => setLocation(id)}
placeholder={'filter lokasi'}
/>
<SearchInput
onChange={(e) => setSearch(e.target.value)}
value={search}
/>
<div className="flex flex-col md:flex-row gap-1 items-center">
<div className="w-full max-w-md">
<LocationSelectionInput
itemSelected={location}
onItemSelected={(id) => setLocation(id)}
placeholder={'filter lokasi'}
/>
</div>
<div className="w-full max-w-md">
<SearchInput
onChange={(e) =>
setSearch(e.target.value)
}
value={search}
/>
</div>
</div>
</div>
<div className="overflow-auto">
@ -210,7 +203,7 @@ export default function Index(props) {
</tbody>
</table>
</div>
<div className="w-full flex items-center justify-center">
<div className="w-full md:flex md:flex-row justify-center">
<Pagination links={links} params={params} />
</div>
</div>

@ -0,0 +1,269 @@
import React, { useRef, useEffect, useState } from 'react'
import { useDebounce } from '@/hooks'
import { usePage } from '@inertiajs/react'
import axios from 'axios'
import { HiChevronDown, HiChevronUp, HiX } from 'react-icons/hi'
import { Spinner } from 'flowbite-react'
export default function SelectionInput(props) {
const ref = useRef()
const {
props: { auth },
} = usePage()
const {
label = '',
itemSelected = null,
onItemSelected = () => {},
disabled = false,
placeholder = '',
error = '',
all = 0,
} = props
const [showItems, setShowItem] = useState([])
const [isSelected, setIsSelected] = useState(true)
const [selected, setSelected] = useState(null)
const [query, setQuery] = useState('')
const q = useDebounce(query, 300)
const [isOpen, setIsOpen] = useState(false)
const [loading, setLoading] = useState(false)
const toggle = () => {
setQuery('')
setIsOpen(!isOpen)
}
const onInputMouseDown = () => {
setIsSelected(false)
setQuery('')
setIsOpen(!isOpen)
}
const handleSelectItem = (item) => {
setIsSelected(true)
onItemSelected(item.id)
setSelected(`${item.location.name} - ${item.name}`)
setIsOpen(false)
}
const removeItem = () => {
setIsSelected(false)
setSelected('')
onItemSelected(null)
}
const filterItems = (value) => {
setIsSelected(false)
setQuery(value)
}
useEffect(() => {
if (isOpen === true) {
const checkIfClickedOutside = (e) => {
if (isOpen && ref.current && !ref.current.contains(e.target)) {
setIsOpen(false)
if (selected !== null) {
setIsSelected(true)
}
}
}
document.addEventListener('mousedown', checkIfClickedOutside)
return () => {
document.removeEventListener('mousedown', checkIfClickedOutside)
}
}
}, [isOpen])
const fetch = (q = '') => {
setLoading(true)
axios
.get(route('api.location-profile.index', { q: q, all: all }), {
headers: {
'Content-Type': 'application/json',
// Authorization: 'Bearer ' + auth.user.jwt_token,
},
})
.then((response) => {
setShowItem(response.data)
})
.catch((err) => {
alert(err)
})
.finally(() => setLoading(false))
}
// every select item open
useEffect(() => {
if (isOpen) {
fetch(q)
}
}, [q, isOpen])
// once page load
useEffect(() => {
fetch()
}, [])
useEffect(() => {
if (disabled) {
setSelected('')
}
}, [disabled])
useEffect(() => {
if (itemSelected !== null) {
const item = showItems.find((item) => item.id === itemSelected)
if (item) {
setSelected(`${item.location.name} - ${item.name}`)
setIsSelected(true)
}
return
}
setIsSelected(false)
}, [itemSelected, loading])
useEffect(() => {
if (isSelected && selected === '') {
setSelected('')
setIsSelected(false)
}
}, [isSelected])
return (
<div className="flex flex-col items-center" ref={ref}>
<div className="w-full flex flex-col items-center">
<div className="w-full">
<div className="flex flex-col relative">
{label !== '' && (
<label
htmlFor="first_name"
className="mb-2 block text-sm font-medium text-gray-900 dark:text-white"
>
{label}
</label>
)}
<div className="w-full">
<div
className={`p-1.5 bg-gray-50 dark:bg-gray-700 flex border rounded-lg
${
error
? 'border-red-500'
: 'border-gray-300 dark:border-gray-600'
}
${disabled ? 'bg-gray-50' : ''}`}
>
<input
className="block w-full text-sm bg-gray-50 text-gray-900 dark:border-gray-700 border cursor-pointer dark:text-gray-300 outline-none border-transparent dark:bg-gray-700 dark:placeholder-gray-400 px-1"
onMouseDown={onInputMouseDown}
placeholder={placeholder}
value={`${
isSelected
? selected === null
? ''
: selected
: query
}`}
onChange={(e) =>
filterItems(e.target.value)
}
disabled={disabled}
/>
{isSelected && (
<div
onClick={
disabled ? () => {} : removeItem
}
>
<button className="cursor-pointer w-6 h-6 text-red-300 outline-none focus:outline-none">
<HiX />
</button>
</div>
)}
<div onClick={disabled ? () => {} : toggle}>
<button className="cursor-pointer w-6 h-6 text-gray-300 outline-none focus:outline-none">
{isOpen ? (
<HiChevronUp />
) : (
<HiChevronDown />
)}
</button>
</div>
</div>
{error && (
<p className="mb-2 text-sm text-red-600 dark:text-red-500">
{error}
</p>
)}
</div>
{isOpen && (
<div
className="absolute mt-1 shadow-lg bg-gray-50 dark:bg-gray-700 dark:text-gray-200 top-100 z-40 w-full lef-0 rounded overflow-y-auto"
style={{ maxHeight: '300px', top: '100%' }}
>
<div className="flex flex-col w-full">
{loading ? (
<div>
<div className="flex w-full items-center p-2 pl-2 border-transparent border-l-2 relative hover:border-neutral-content">
<div className="w-full items-center justify-center flex">
<div className="mx-2 my-5">
<Spinner className="mr-2" />
<span>Loading...</span>
</div>
</div>
</div>
</div>
) : (
<>
{showItems.map((item, index) => (
<div
key={index}
onClick={() =>
handleSelectItem(item)
}
>
<div className="flex w-full items-center p-2 pl-2 border-transparent border-l-2 relative hover:border-neutral-content hover:bg-gray-400 bg-opacity-10 dark:hover:bg-gray-200 dark:hover:bg-opacity-10 dark:hover:text-gray-100">
<div className="w-full items-center flex">
<div className="mx-2">
<span>
{
item
.location
.name
}{' '}
-{' '}
{item.name}
</span>
</div>
</div>
</div>
</div>
))}
{showItems.length <= 0 && (
<div>
<div className="flex w-full items-center p-2 pl-2 border-transparent border-l-2 relative hover:border-neutral-content">
<div className="w-full items-center justify-center flex">
<div className="mx-2 my-5">
<span>
No Items
Found
</span>
</div>
</div>
</div>
</div>
)}
</>
)}
</div>
</div>
)}
</div>
</div>
</div>
</div>
)
}

@ -1,27 +1,23 @@
import React from 'react';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head } from '@inertiajs/react';
import { HiFire } from 'react-icons/hi';
import React from 'react'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import { Head } from '@inertiajs/react'
import { HiFire } from 'react-icons/hi'
export default function Maintance(props) {
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={"Page"}
action={"Development Mode"}
>
<AuthenticatedLayout page={'Page'} action={'Development Mode'}>
<Head title="Masih Dalam Pengembangan" />
<div>
<div className="mx-auto sm:px-6 lg:px-8 text-center">
<div className="overflow-hidden py-40 shadow-sm sm:rounded-lg bg-white dark:bg-gray-800 flex justify-center items-center flex-col">
<HiFire className='text-center dark:text-white w-40 h-40'/>
<div className="p-6 dark:text-gray-100 text-3xl">Fitur Dalam Pengembangan</div>
<HiFire className="text-center dark:text-white w-40 h-40" />
<div className="p-6 dark:text-gray-100 text-3xl">
Fitur Dalam Pengembangan
</div>
</div>
</div>
</div>
</AuthenticatedLayout>
);
}
)
}

@ -18,13 +18,7 @@ export default function Index(props) {
router.get(route(route().current()))
}
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Notifikasi'}
action={''}
>
<AuthenticatedLayout page={'Notifikasi'} action={''}>
<Head title="Notifikasi" />
<div>

@ -41,13 +41,7 @@ export default function Info(props) {
const canDelete = hasPermission(auth, 'delete-poin-reward')
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Bonus Poin'}
action={''}
>
<AuthenticatedLayout page={'Bonus Poin'} action={''}>
<Head title="Bonus Poin" />
<div>

@ -1,92 +1,107 @@
import React, { useEffect, useState } from 'react';
import { Head, Link, useForm, usePage } from '@inertiajs/react';
import React, { useEffect, useState } from 'react'
import { Head, Link, useForm, usePage } from '@inertiajs/react'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import FormInput from '@/Components/FormInput';
import Button from '@/Components/Button';
import { isEmpty } from 'lodash';
import Checkbox from '@/Components/Checkbox';
import { router } from '@inertiajs/react';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import FormInput from '@/Components/FormInput'
import Button from '@/Components/Button'
import { isEmpty } from 'lodash'
import Checkbox from '@/Components/Checkbox'
import { router } from '@inertiajs/react'
export default function Role(props) {
const { props: { errors } } = usePage()
const {
props: { errors },
} = usePage()
const { permissions, role } = props
const [processing, setProcessing] = useState(false)
const [name, setName] = useState('')
const [permins, setPermins] = useState(permissions.map(permin => { return {...permin, checked: false} }))
const [permins, setPermins] = useState(
permissions.map((permin) => {
return { ...permin, checked: false }
})
)
const handleCheckPermission = (e) => {
setPermins(permins.map(item => {
if(item.name === e.target.name) {
return {
...item,
checked: !item.checked
setPermins(
permins.map((item) => {
if (item.name === e.target.name) {
return {
...item,
checked: !item.checked,
}
}
}
return item
}))
return item
})
)
}
const handleCheckAll = (e) => {
setPermins(permins.map(item => {
return {
...item,
checked: e.target.checked,
}
}))
setPermins(
permins.map((item) => {
return {
...item,
checked: e.target.checked,
}
})
)
}
const handleSubmit = () => {
if(isEmpty(role) === false) {
router.put(route('roles.update', role), {
if (isEmpty(role) === false) {
router.put(
route('roles.update', role),
{
name: name,
permissions: permins.filter((item) => item.checked),
},
{
onStart: () => setProcessing(true),
onFinish: (e) => {
setProcessing(false)
},
}
)
return
}
router.post(
route('roles.store'),
{
name: name,
permissions: permins.filter(item => item.checked)
}, {
permissions: permins.filter((item) => item.checked),
},
{
onStart: () => setProcessing(true),
onFinish: (e) => {
setProcessing(false)
}
})
return
}
router.post(route('roles.store'), {
name: name,
permissions: permins.filter(item => item.checked)
}, {
onStart: () => setProcessing(true),
onFinish: (e) => {
setProcessing(false)
},
}
})
)
}
useEffect(() => {
if(!isEmpty(role)) {
if (!isEmpty(role)) {
setName(role.name)
setPermins(permins.map(item => {
const isExists = role.permissions.find(permit => permit.id === item.id)
if (isExists) {
return {
...item,
checked: true,
setPermins(
permins.map((item) => {
const isExists = role.permissions.find(
(permit) => permit.id === item.id
)
if (isExists) {
return {
...item,
checked: true,
}
}
}
return item
}))
return item
})
)
}
}, [role])
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'System'}
action={'Role'}
>
<AuthenticatedLayout page={'System'} action={'Role'}>
<Head title="Role" />
<div>
@ -95,18 +110,22 @@ export default function Role(props) {
<FormInput
name="name"
value={name}
onChange={e => setName(e.target.value)}
onChange={(e) => setName(e.target.value)}
label="Nama"
error={errors.name}
/>
<Checkbox
label={"Check All"}
label={'Check All'}
onChange={handleCheckAll}
/>
<div
className={`grid grid-cols-1 md:grid-cols-4 border border-rounded border-gray-400 rounded-lg p-2 gap-2 ${errors.permissions ? 'border-red-600' : 'border-gray-400'}`}
<div
className={`grid grid-cols-1 md:grid-cols-4 border border-rounded border-gray-400 rounded-lg p-2 gap-2 ${
errors.permissions
? 'border-red-600'
: 'border-gray-400'
}`}
>
{permins.map(item => (
{permins.map((item) => (
<Checkbox
key={item.id}
label={item.label}
@ -117,26 +136,24 @@ export default function Role(props) {
))}
</div>
{errors.permissions && (
<p className="mb-2 text-sm text-red-600 dark:text-red-500">{errors.permissions}</p>
<p className="mb-2 text-sm text-red-600 dark:text-red-500">
{errors.permissions}
</p>
)}
<div className="flex items-center">
<Button
onClick={handleSubmit}
processing={processing}
>
Simpan
</Button>
<Link href={route('roles.index')}>
<Button
type="secondary"
onClick={handleSubmit}
processing={processing}
>
Kembali
Simpan
</Button>
</Link>
</div>
<Link href={route('roles.index')}>
<Button type="secondary">Kembali</Button>
</Link>
</div>
</div>
</div>
</div>
</AuthenticatedLayout>
);
)
}

@ -1,20 +1,23 @@
import React, { useEffect, useState } from 'react';
import { router } from '@inertiajs/react';
import { usePrevious } from 'react-use';
import { Head, Link } from '@inertiajs/react';
import { Button, Dropdown } from 'flowbite-react';
import { HiPencil, HiTrash } from 'react-icons/hi';
import { useModalState } from '@/hooks';
import React, { useEffect, useState } from 'react'
import { router } from '@inertiajs/react'
import { usePrevious } from 'react-use'
import { Head, Link } from '@inertiajs/react'
import { Button, Dropdown } from 'flowbite-react'
import { HiPencil, HiTrash } from 'react-icons/hi'
import { useModalState } from '@/hooks'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import Pagination from '@/Components/Pagination';
import ModalConfirm from '@/Components/ModalConfirm';
import SearchInput from '@/Components/SearchInput';
import { hasPermission } from '@/utils';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import Pagination from '@/Components/Pagination'
import ModalConfirm from '@/Components/ModalConfirm'
import SearchInput from '@/Components/SearchInput'
import { hasPermission } from '@/utils'
export default function Product(props) {
const { data: { links, data }, auth } = props
const {
data: { links, data },
auth,
} = props
const [search, setSearch] = useState('')
const preValue = usePrevious(search)
@ -26,7 +29,7 @@ export default function Product(props) {
}
const onDelete = () => {
if(confirmModal.data !== null) {
if (confirmModal.data !== null) {
router.delete(route('roles.destroy', confirmModal.data.id))
}
}
@ -50,52 +53,58 @@ export default function Product(props) {
const canDelete = hasPermission(auth, 'delete-role')
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'System'}
action={'Role'}
>
<Head title="Role" />
<AuthenticatedLayout page={'Admin'} action={'Rule'}>
<Head title="Admin" />
<div>
<div className="mx-auto sm:px-6 lg:px-8 ">
<div className="p-6 overflow-hidden shadow-sm sm:rounded-lg bg-gray-200 dark:bg-gray-800 space-y-4">
<div className='flex justify-between'>
<div className="flex justify-between">
{canCreate && (
<Link href={route('roles.create')}>
<Button size="sm">Tambah</Button>
</Link>
)}
<div className="flex items-center">
<SearchInput
onChange={e => setSearch(e.target.value)}
onChange={(e) => setSearch(e.target.value)}
value={search}
/>
</div>
</div>
<div className='overflow-auto'>
<div className="overflow-auto">
<div>
<table className="w-full text-sm text-left text-gray-500 dark:text-gray-400 mb-4">
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th scope="col" className="py-3 px-6">
<th
scope="col"
className="py-3 px-6"
>
Nama
</th>
<th scope="col" className="py-3 px-6"/>
<th
scope="col"
className="py-3 px-6"
/>
</tr>
</thead>
<tbody>
{data.map(role => (
<tr className="bg-white border-b dark:bg-gray-800 dark:border-gray-700" key={role.id}>
<td scope="row" className="py-4 px-6 font-medium text-gray-900 whitespace-nowrap dark:text-white">
{data.map((role) => (
<tr
className="bg-white border-b dark:bg-gray-800 dark:border-gray-700"
key={role.id}
>
<td
scope="row"
className="py-4 px-6 font-medium text-gray-900 whitespace-nowrap dark:text-white"
>
{role.name}
</td>
<td className="py-4 px-6 flex justify-end">
<Dropdown
label={"Opsi"}
label={'Opsi'}
floatingArrow={true}
arrowIcon={true}
dismissOnClick={true}
@ -103,19 +112,36 @@ export default function Product(props) {
>
{canUpdate && (
<Dropdown.Item
onClick={() => router.visit(route('roles.edit', role))}
onClick={() =>
router.visit(
route(
'roles.edit',
role
)
)
}
>
<div className='flex space-x-1 items-center'>
<HiPencil/>
<div>Ubah</div>
<div className="flex space-x-1 items-center">
<HiPencil />
<div>
Ubah
</div>
</div>
</Dropdown.Item>
)}
{canDelete && (
<Dropdown.Item onClick={() => handleDeleteClick(role)}>
<div className='flex space-x-1 items-center'>
<HiTrash/>
<div>Hapus</div>
<Dropdown.Item
onClick={() =>
handleDeleteClick(
role
)
}
>
<div className="flex space-x-1 items-center">
<HiTrash />
<div>
Hapus
</div>
</div>
</Dropdown.Item>
)}
@ -126,17 +152,14 @@ export default function Product(props) {
</tbody>
</table>
</div>
<div className='w-full flex items-center justify-center'>
<Pagination links={links} params={params}/>
<div className="w-full flex items-center justify-center">
<Pagination links={links} params={params} />
</div>
</div>
</div>
</div>
</div>
<ModalConfirm
modalState={confirmModal}
onConfirm={onDelete}
/>
<ModalConfirm modalState={confirmModal} onConfirm={onDelete} />
</AuthenticatedLayout>
);
)
}

@ -12,13 +12,7 @@ export default function Detail(props) {
const { sale } = props
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={`Sale`}
action={`Invoice #${sale.code}`}
>
<AuthenticatedLayout page={`Sale`} action={`Invoice #${sale.code}`}>
<Head title={`Invoice #${sale.code}`} />
<div>

@ -31,13 +31,7 @@ export default function Info(props) {
}, [search])
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Sale'}
action={''}
>
<AuthenticatedLayout page={'Sale'} action={''}>
<Head title="Sale" />
<div>

@ -44,9 +44,6 @@ export default function General(props) {
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Setting'}
action={''}
parent={route(route().current())}

@ -60,14 +60,8 @@ export default function User(props) {
const canDelete = hasPermission(auth, 'delete-user')
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'System'}
action={'User'}
>
<Head title="User" />
<AuthenticatedLayout page={'Admin'} action={'List'}>
<Head title="Admin" />
<div>
<div className="mx-auto sm:px-6 lg:px-8 ">

@ -30,13 +30,7 @@ export default function Form(props) {
}
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Verifikasi Customer'}
action={'Form'}
>
<AuthenticatedLayout page={'Verifikasi Customer'} action={'Form'}>
<Head title="Verifikasi Customer" />
<div>

@ -31,13 +31,7 @@ export default function Index(props) {
}, [search])
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Verifikasi Customer'}
action={''}
>
<AuthenticatedLayout page={'Verifikasi Customer'} action={''}>
<Head title="Verifikasi Customer" />
<div>

@ -109,13 +109,7 @@ export default function Form(props) {
}, [voucher])
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Voucher'}
action={'Form'}
>
<AuthenticatedLayout page={'Voucher'} action={'Form'}>
<Head title="Voucher" />
<div>

@ -6,55 +6,16 @@ import FormInput from '@/Components/FormInput'
import Button from '@/Components/Button'
import { Head, useForm } from '@inertiajs/react'
import FormInputWith from '@/Components/FormInputWith'
import LocationSelectionInput from '../Location/SelectionInput'
import LocationProfileSelectionInput from '../LocationProfile/SelectionInput'
import TextArea from '@/Components/TextArea'
import Checkbox from '@/Components/Checkbox'
export default function Import(props) {
const { levels } = props
const [use_level, setUseLevel] = useState(false)
const { data, setData, post, processing, errors } = useForm({
script: '',
discount: 0,
display_price: 0,
price_poin: 0,
expired: '',
expired_unit: 'Hari',
location_id: null,
prices: null,
location_profile_id: null,
})
const handleUseLevel = () => {
setUseLevel(!use_level)
if (!use_level === true) {
const prices = levels.map((level) => {
return {
name: level.name,
customer_level_id: level.id,
display_price: '0',
}
})
setData('prices', prices)
return
}
setData('prices', null)
}
const handleSetLevelPrice = (id, value) => {
setData(
'prices',
data.prices.map((price) => {
if (price.customer_level_id === id) {
return {
...price,
display_price: value,
}
}
return price
})
)
}
const handleOnChange = (event) => {
setData(
event.target.name,
@ -65,119 +26,29 @@ export default function Import(props) {
: event.target.value
)
}
const handleSubmit = () => {
post(route('voucher.import'))
}
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Voucher'}
action={'Import'}
>
<AuthenticatedLayout page={'Voucher'} action={'Import'}>
<Head title="Voucher" />
<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">Voucher</div>
<LocationSelectionInput
label="Lokasi"
itemSelected={data.location_id}
onItemSelected={(id) => setData('location_id', id)}
error={errors.location_id}
<LocationProfileSelectionInput
label="Profile Lokasi"
itemSelected={data.location_profile_id}
onItemSelected={(id) =>
setData('location_profile_id', id)
}
error={errors.location_profile_id}
/>
<div className="mt-2" />
<FormInput
type="number"
name="display_price"
value={data.display_price}
onChange={handleOnChange}
label="Harga"
error={errors.display_price}
/>
<FormInputWith
type="number"
rightItem={<div className="text-sm">%</div>}
name="discount"
value={data.discount}
onChange={handleOnChange}
error={errors.discount}
formClassName={'pr-10'}
label="Discount"
max={100}
min={0}
/>
<FormInput
type="number"
name="price_poin"
value={data.price_poin}
onChange={handleOnChange}
label="Harga poin (untuk penukaran)"
error={errors.price_poin}
/>
<div>
<label className="block text-sm font-medium text-gray-900 dark:text-white">
Masa Aktif
</label>
<div className="w-full flex flex-row space-x-2 items-center">
<FormInput
type="number"
name="expired"
value={data.expired}
onChange={handleOnChange}
// label="Masa Aktif"
error={errors.expired}
className="flex-1"
/>
<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.expired_unit}
name="expired_unit"
>
<option value="Jam">Jam</option>
<option value="Hari">Hari</option>
<option value="Minggu">Minggu</option>
<option value="Bulan">Bulan</option>
<option value="Tahun">Tahun</option>
</select>
</div>
</div>
</div>
<Checkbox
label="Level Harga"
value={use_level}
onChange={(e) => handleUseLevel(e.target.value)}
/>
<div
className={`p-2 my-2 border rounded ${
!use_level && 'invisible'
}`}
>
{data.prices?.map((price) => (
<FormInput
type="number"
key={price.customer_level_id}
value={price.display_price}
onChange={(e) =>
handleSetLevelPrice(
price.customer_level_id,
e.target.value
)
}
label={price.name}
/>
))}
{errors.prices && (
<p className="mb-2 text-sm text-red-600 dark:text-red-500">
{errors.prices}
</p>
)}
</div>
<TextArea
name="script"
value={data.script}

@ -6,22 +6,23 @@ import { Button, Dropdown } from 'flowbite-react'
import { HiPencil, HiTrash } from 'react-icons/hi'
import { useModalState } from '@/hooks'
import { hasPermission, formatIDR } 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 { hasPermission } from '@/utils'
export default function Index(props) {
const {
query: { links, data },
auth,
location,
profile,
stats,
} = props
const [location, setLocation] = useState(null)
const [search, setSearch] = useState('')
const preValue = usePrevious(`${search}${location}`)
const preValue = usePrevious(`${search}`)
const confirmModal = useModalState()
@ -36,19 +37,19 @@ export default function Index(props) {
}
}
const params = { q: search, location_id: location }
const params = { q: search }
useEffect(() => {
if (preValue) {
router.get(
route(route().current()),
{ q: search, location_id: location },
{ q: search },
{
replace: true,
preserveState: true,
}
)
}
}, [search, location])
}, [search])
const canCreate = hasPermission(auth, 'create-voucher')
const canUpdate = hasPermission(auth, 'update-voucher')
@ -56,16 +57,64 @@ export default function Index(props) {
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Voucher'}
action={''}
action={[location.name, profile.name]}
parent={route('voucher.profile', location)}
>
<Head title="Voucher" />
<div>
<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 Voucher
</div>
<div className="font-bold text-xl pt-2">
{formatIDR(stats.count_voucher_total)} PCS
</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 Voucher
</div>
<div className="font-bold text-xl pt-2">
Rp. {formatIDR(stats.sum_voucher_total)}
</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 Voucher sudah terjual
</div>
<div className="font-bold text-xl pt-2">
{formatIDR(stats.count_voucher_sold)} PCS
</div>
</div>
<div className="border rounded-md shadow bg-white px-4 py-2 flex flex-col">
<div className="text-gray-600 text-lg">
Jumlah Voucher sudah terjual
</div>
<div className="font-bold text-xl pt-2">
Rp. {formatIDR(stats.sum_voucher_sold)}
</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 Voucher belum terjual
</div>
<div className="font-bold text-xl pt-2">
{formatIDR(stats.count_voucher_unsold)} PCS
</div>
</div>
<div className="border rounded-md shadow bg-white px-4 py-2 flex flex-col">
<div className="text-gray-600 text-lg">
Jumlah Voucher belum terjual
</div>
<div className="font-bold text-xl pt-2">
Rp. {formatIDR(stats.sum_voucher_unsold)}
</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 && (
@ -81,11 +130,6 @@ export default function Index(props) {
</div>
)}
<div className="flex flex-row space-x-2 items-center">
<LocationSelectionInput
itemSelected={location}
onItemSelected={(id) => setLocation(id)}
placeholder={'filter lokasi'}
/>
<SearchInput
onChange={(e) => setSearch(e.target.value)}
value={search}
@ -113,19 +157,19 @@ export default function Index(props) {
scope="col"
className="py-3 px-6"
>
Username
Kode
</th>
<th
scope="col"
className="py-3 px-6"
>
Password
Profile
</th>
<th
scope="col"
className="py-3 px-6"
>
Profile
Kuota
</th>
<th
scope="col"
@ -137,7 +181,7 @@ export default function Index(props) {
scope="col"
className="py-3 px-6"
>
Kuota
Created At
</th>
<th
scope="col"
@ -145,6 +189,7 @@ export default function Index(props) {
>
Terjual
</th>
<th
scope="col"
className="py-3 px-6 w-1/8"
@ -167,8 +212,12 @@ export default function Index(props) {
scope="row"
className="py-4 px-6"
>
{voucher.location.name}
{
voucher.profile.location
.name
}
</td>
<td
scope="row"
className="py-4 px-6"
@ -179,13 +228,13 @@ export default function Index(props) {
scope="row"
className="py-4 px-6"
>
{voucher.password}
{voucher.profile.name}
</td>
<td
scope="row"
className="py-4 px-6"
>
{voucher.profile}
{voucher.profile.quota}
</td>
<td
scope="row"
@ -197,16 +246,21 @@ export default function Index(props) {
scope="row"
className="py-4 px-6"
>
{voucher.display_quota}
{
voucher.created_at_formated
}
</td>
<td
scope="row"
className="py-4 px-6"
>
{+voucher.is_sold === 1
? 'Ya'
: 'Tidak'}
<div
className={`p-2 border font-bold ${voucher.status.color}`}
>
{voucher.status.text}
</div>
</td>
<td className="py-4 px-6 flex justify-end">
<Dropdown
label={'Opsi'}

@ -0,0 +1,102 @@
import React, { useEffect, useState } from 'react'
import { router, Link } from '@inertiajs/react'
import { usePrevious } from 'react-use'
import { Head } from '@inertiajs/react'
import { Button } from 'flowbite-react'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import Pagination from '@/Components/Pagination'
import SearchInput from '@/Components/SearchInput'
import { hasPermission } from '@/utils'
export default function Index(props) {
const {
query: { links, data },
auth,
} = props
const [search, setSearch] = useState('')
const preValue = usePrevious(`${search}`)
const params = { q: search }
useEffect(() => {
if (preValue) {
router.get(
route(route().current()),
{ q: search },
{
replace: true,
preserveState: true,
}
)
}
}, [search])
const canCreate = hasPermission(auth, 'create-voucher')
return (
<AuthenticatedLayout page={'Voucher'} action={''}>
<Head title="Voucher" />
<div>
<div className="mx-auto sm:px-6 lg:px-8 ">
<div className="p-6 overflow-hidden shadow-sm sm:rounded-lg bg-gray-200 dark:bg-gray-800 space-y-4">
<div className="flex flex-col lg:flex-row gap-1 justify-between">
{canCreate && (
<div className="flex flex-row space-x-2">
<Link href={route('voucher.create')}>
<Button size="sm">Tambah</Button>
</Link>
<Link href={route('voucher.import')}>
<Button size="sm" outline>
Import
</Button>
</Link>
</div>
)}
<div className="flex flex-col md:flex-row gap-1 items-center">
<div className="w-full max-w-md">
<SearchInput
onChange={(e) =>
setSearch(e.target.value)
}
value={search}
/>
</div>
</div>
</div>
<div className="overflow-auto">
<div className="grid grid-cols-2 lg:grid-cols-4 gap-2 mb-4">
{data.map((location) => (
<Link
key={location.name}
href={route(
'voucher.profile',
location
)}
className="px-4 py-6 bg-white flex flex-col md:flex-row justify-between items-center rounded-md shadow border hover:underline hover:bg-gray-100"
>
<div className="text-xl font-bold hover:underline">
{location.name}
</div>
<div
className={`${location.count_vouchers.color} px-2 rounded border`}
>
{
location.count_vouchers
.unsold_count
}
</div>
</Link>
))}
</div>
<div className="w-full flex items-center justify-center">
<Pagination links={links} params={params} />
</div>
</div>
</div>
</div>
</div>
</AuthenticatedLayout>
)
}

@ -0,0 +1,159 @@
import React, { useEffect, useState } from 'react'
import { router, Link } from '@inertiajs/react'
import { usePrevious } from 'react-use'
import { Head } from '@inertiajs/react'
import { Button } from 'flowbite-react'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import Pagination from '@/Components/Pagination'
import SearchInput from '@/Components/SearchInput'
import { formatIDR, hasPermission } from '@/utils'
export default function Index(props) {
const {
query: { links, data },
location,
stats,
auth,
} = props
const [search, setSearch] = useState('')
const preValue = usePrevious(`${search}`)
const params = { q: search }
useEffect(() => {
if (preValue) {
router.get(
route(route().current()),
{ q: search },
{
replace: true,
preserveState: true,
}
)
}
}, [search])
const canCreate = hasPermission(auth, 'create-voucher')
return (
<AuthenticatedLayout
page={'Voucher'}
action={[location.name]}
parent={route('voucher.location')}
>
<Head title="Voucher" />
<div>
<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 Voucher
</div>
<div className="font-bold text-xl pt-2">
{formatIDR(stats.count_voucher_total)} PCS
</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 Voucher
</div>
<div className="font-bold text-xl pt-2">
Rp. {formatIDR(stats.sum_voucher_total)}
</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 Voucher sudah terjual
</div>
<div className="font-bold text-xl pt-2">
{formatIDR(stats.count_voucher_sold)} PCS
</div>
</div>
<div className="border rounded-md shadow bg-white px-4 py-2 flex flex-col">
<div className="text-gray-600 text-lg">
Jumlah Voucher sudah terjual
</div>
<div className="font-bold text-xl pt-2">
Rp. {formatIDR(stats.sum_voucher_sold)}
</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 Voucher belum terjual
</div>
<div className="font-bold text-xl pt-2">
{formatIDR(stats.count_voucher_unsold)} PCS
</div>
</div>
<div className="border rounded-md shadow bg-white px-4 py-2 flex flex-col">
<div className="text-gray-600 text-lg">
Jumlah Voucher belum terjual
</div>
<div className="font-bold text-xl pt-2">
Rp. {formatIDR(stats.sum_voucher_unsold)}
</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 flex-col lg:flex-row gap-1 justify-between">
{canCreate && (
<div className="flex flex-row space-x-2">
<Link href={route('voucher.create')}>
<Button size="sm">Tambah</Button>
</Link>
<Link href={route('voucher.import')}>
<Button size="sm" outline>
Import
</Button>
</Link>
</div>
)}
<div className="flex flex-col md:flex-row gap-1 items-center">
<div className="w-full max-w-md">
<SearchInput
onChange={(e) =>
setSearch(e.target.value)
}
value={search}
/>
</div>
</div>
</div>
<div className="overflow-auto">
<div className="grid grid-cols-2 lg:grid-cols-4 gap-2 mb-4">
{data.map((profile) => (
<Link
key={profile.name}
href={route('voucher.index', {
location,
profile,
})}
className="px-4 py-6 bg-white flex flex-col md:flex-row justify-between items-center rounded-md shadow border hover:bg-gray-100"
>
<div className="text-xl font-bold hover:underline">
{profile.name}
</div>
<div
className={`${profile.count_vouchers.color} px-2 rounded border`}
>
{
profile.count_vouchers
.unsold_count
}
</div>
</Link>
))}
</div>
<div className="w-full flex items-center justify-center">
<Pagination links={links} params={params} />
</div>
</div>
</div>
</div>
</div>
</AuthenticatedLayout>
)
}

@ -15,7 +15,7 @@
@vite(['resources/js/admin.jsx', "resources/js/Pages/{$page['component']}.jsx"])
@inertiaHead
</head>
<body class="font-sans antialiased">
<body class="font-sans antialiased" creator="aji.kamaludin2021@gmail.com">
@inertia
</body>
</html>

@ -18,7 +18,7 @@
@vite(['resources/js/app.jsx', "resources/js/Customer/{$page['component']}.jsx"])
@inertiaHead
</head>
<body class="font-sans antialiased">
<body class="font-sans antialiased" creator="aji.kamaludin2021@gmail.com">
@inertia
</body>
</html>

@ -113,12 +113,14 @@ Route::middleware(['http_secure_aware', 'inertia.admin'])
// voucher
Route::get('/vouchers/import', [VoucherController::class, 'form_import'])->name('voucher.form_import');
Route::post('/vouchers/import', [VoucherController::class, 'import'])->name('voucher.import');
Route::get('/vouchers', [VoucherController::class, 'index'])->name('voucher.index');
Route::get('/vouchers/create', [VoucherController::class, 'create'])->name('voucher.create');
Route::post('/vouchers', [VoucherController::class, 'store'])->name('voucher.store');
Route::get('/vouchers/{voucher}', [VoucherController::class, 'edit'])->name('voucher.edit');
Route::post('/vouchers/{voucher}', [VoucherController::class, 'update'])->name('voucher.update');
Route::get('/vouchers/{voucher}/edit', [VoucherController::class, 'edit'])->name('voucher.edit');
Route::post('/vouchers/{voucher}/edit', [VoucherController::class, 'update'])->name('voucher.update');
Route::delete('/vouchers/{voucher}', [VoucherController::class, 'destroy'])->name('voucher.destroy');
Route::get('/vouchers', [VoucherController::class, 'location'])->name('voucher.location');
Route::get('/vouchers/{location}', [VoucherController::class, 'profile'])->name('voucher.profile');
Route::get('/vouchers/{location}/{profile}', [VoucherController::class, 'index'])->name('voucher.index');
// setting
Route::get('/settings', [SettingController::class, 'index'])->name('setting.index');

@ -2,6 +2,7 @@
use App\Http\Controllers\Api\CustomerController;
use App\Http\Controllers\Api\LocationController;
use App\Http\Controllers\Api\LocationProfileController;
use App\Http\Controllers\Api\NotificationController;
use App\Http\Controllers\Api\RoleController;
use App\Http\Controllers\Customer\DepositController;
@ -26,6 +27,7 @@ Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
// general
Route::get('/roles', [RoleController::class, 'index'])->name('api.role.index');
Route::get('/locations', [LocationController::class, 'index'])->name('api.location.index');
Route::get('/location-profiles', [LocationProfileController::class, 'index'])->name('api.location-profile.index');
Route::get('/customers', [CustomerController::class, 'index'])->name('api.customer.index');
Route::get('/notifications/{notif?}', [NotificationController::class, 'update'])->name('api.notification.update');

@ -1,22 +1,23 @@
const defaultTheme = require('tailwindcss/defaultTheme');
const defaultTheme = require('tailwindcss/defaultTheme')
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php",
"./resources/views/**/*.blade.php",
"./resources/js/**/*.jsx",
"./node_modules/flowbite/**/*.js",
"./node_modules/flowbite-react/**/*.{js,jsx,ts,tsx}",
'./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}',
],
theme: {
extend: {
fontFamily: {
sans: ["Nunito", ...defaultTheme.fontFamily.sans],
sans: ['Nunito', ...defaultTheme.fontFamily.sans],
},
},
},
plugins: [require("@tailwindcss/forms"), require("flowbite/plugin")],
};
plugins: [require('@tailwindcss/forms'), require('flowbite/plugin')],
}

Loading…
Cancel
Save