location profile done

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

@ -73,7 +73,7 @@ class AuthController extends Controller
'fullname' => $user->name,
'name' => $user->nickname,
'email' => $user->email,
'username' => Str::slug($user->name . '_' . Str::random(5), '_'),
'username' => Str::slug($user->name.'_'.Str::random(5), '_'),
'google_id' => $user->id,
'google_oauth_response' => json_encode($user),
]);
@ -126,7 +126,7 @@ class AuthController extends Controller
$bonuspoin = Setting::getByKey('AFFILATE_poin_AMOUNT');
$poin = $refferal->poins()->create([
'debit' => $bonuspoin,
'description' => 'Bonus Refferal #' . Str::random(5),
'description' => 'Bonus Refferal #'.Str::random(5),
]);
$poin->update_customer_balance();

@ -3,9 +3,9 @@
namespace App\Http\Controllers\Customer;
use App\Http\Controllers\Controller;
use App\Models\PoinReward;
use App\Models\Customer;
use App\Models\DepositHistory;
use App\Models\PoinReward;
use App\Models\Sale;
use App\Models\Voucher;
use Illuminate\Http\Request;
@ -166,13 +166,13 @@ class CartController extends Controller
if ($bonus != null) {
$poin = $customer->poins()->create([
'debit' => $bonus->bonus_poin,
'description' => 'Bonus Pembelian #' . $sale->code,
'description' => 'Bonus Pembelian #'.$sale->code,
]);
$poin->update_customer_balance();
}
$description = 'Pembayaran #' . $sale->code;
$description = 'Pembayaran #'.$sale->code;
if ($customer->deposit_balance < $total) {
if ($customer->deposit_balance > 0) {

@ -19,7 +19,6 @@ class HomeController extends Controller
$locations = Location::orderBy('updated_at', 'desc')->get();
$vouchers = Voucher::with(['location'])
->where('is_sold', Voucher::UNSOLD)
->groupBy('batch_id')
->orderBy('updated_at', 'desc');
if ($request->location_id != '') {

@ -52,7 +52,7 @@ class PoinExchangeController extends Controller
DB::beginTransaction();
$sale = $customer->sales()->create([
'code' => 'Tukar poin ' . str()->upper(str()->random(5)),
'code' => 'Tukar poin '.str()->upper(str()->random(5)),
'date_time' => now(),
'amount' => 0,
'payed_with' => Sale::PAYED_WITH_poin,

@ -9,7 +9,6 @@ use App\Models\SaleItem;
use App\Models\Voucher;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class GeneralController extends Controller
@ -24,9 +23,9 @@ class GeneralController extends Controller
$month = now()->locale('id')->translatedFormat('F');
$startOfMonth = now()->startOfMonth()->format('m/d/Y');
$endOfMonth = now()->endOfMonth()->format('m/d/Y');
$total_voucher_sale_this_month = SaleItem::whereBetween("created_at", [$startOfMonth, $endOfMonth])
$total_voucher_sale_this_month = SaleItem::whereBetween('created_at', [$startOfMonth, $endOfMonth])
->sum('price');
$count_voucher_sale_this_month = SaleItem::whereBetween("created_at", [$startOfMonth, $endOfMonth])
$count_voucher_sale_this_month = SaleItem::whereBetween('created_at', [$startOfMonth, $endOfMonth])
->sum('quantity');
$total_voucher_sale_this_day = SaleItem::whereDate('created_at', now()->format('Y-m-d'))
->sum('price');

@ -0,0 +1,169 @@
<?php
namespace App\Http\Controllers;
use App\Models\CustomerLevel;
use App\Models\LocationProfile;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class LocationProfileController extends Controller
{
public function index(Request $request)
{
$query = LocationProfile::with(['location'])->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('LocationProfile/Index', [
'query' => $query->paginate(),
]);
}
public function create()
{
return inertia('LocationProfile/Form', [
'expireds' => LocationProfile::EXPIRED_UNIT,
'levels' => CustomerLevel::all(),
]);
}
public function store(Request $request)
{
$request->validate([
'location_id' => 'required|exists:locations,id',
'name' => 'required|string',
'quota' => 'required|string',
'display_note' => 'nullable|string',
'expired' => 'required|numeric',
'expired_unit' => 'required|string',
'description' => 'nullable|string',
'min_stock' => 'required|numeric',
'display_price' => 'required|numeric',
'discount' => 'required|numeric|min:0|max:100',
'price_poin' => 'required|numeric',
'bonus_poin' => 'required|numeric',
'prices' => 'nullable|array',
'prices.*.customer_level_id' => 'required|exists:customer_levels,id',
'prices.*.display_price' => 'required|numeric',
'prices.*.discount' => 'required|numeric|min:0|max:100',
'prices.*.price_poin' => 'required|numeric',
'prices.*.bonus_poin' => 'required|numeric',
]);
DB::beginTransaction();
$profile = LocationProfile::create([
'location_id' => $request->location_id,
'name' => $request->name,
'quota' => $request->quota,
'display_note' => $request->display_note,
'expired' => $request->expired,
'expired_unit' => $request->expired_unit,
'description' => $request->description,
'min_stock' => $request->min_stock,
'display_price' => $request->display_price,
'discount' => $request->discount,
'price_poin' => $request->price_poin,
'bonus_poin' => $request->bonus_poin,
]);
$prices = collect($request->prices);
if ($prices->count() > 0) {
foreach ($prices as $price) {
$profile->prices()->create([
'customer_level_id' => $price['customer_level_id'],
'display_price' => $price['display_price'],
'discount' => $price['discount'],
'price_poin' => $price['price_poin'],
'bonus_poin' => $price['bonus_poin'],
]);
}
}
DB::commit();
return redirect()->route('location-profile.index')
->with('message', ['type' => 'success', 'message' => 'Item has beed saved']);
}
public function edit(LocationProfile $profile)
{
return inertia('LocationProfile/Form', [
'expireds' => LocationProfile::EXPIRED_UNIT,
'levels' => CustomerLevel::all(),
'profile' => $profile->load(['location', 'prices.level'])
]);
}
public function update(Request $request, LocationProfile $profile)
{
$request->validate([
'location_id' => 'required|exists:locations,id',
'name' => 'required|string',
'quota' => 'required|string',
'display_note' => 'nullable|string',
'expired' => 'required|numeric',
'expired_unit' => 'required|string',
'description' => 'nullable|string',
'min_stock' => 'required|numeric',
'display_price' => 'required|numeric',
'discount' => 'required|numeric|min:0|max:100',
'price_poin' => 'required|numeric',
'bonus_poin' => 'required|numeric',
'prices' => 'nullable|array',
'prices.*.customer_level_id' => 'required|exists:customer_levels,id',
'prices.*.display_price' => 'required|numeric',
'prices.*.discount' => 'required|numeric|min:0|max:100',
'prices.*.price_poin' => 'required|numeric',
'prices.*.bonus_poin' => 'required|numeric',
]);
DB::beginTransaction();
$profile->update([
'location_id' => $request->location_id,
'name' => $request->name,
'quota' => $request->quota,
'display_note' => $request->display_note,
'expired' => $request->expired,
'expired_unit' => $request->expired_unit,
'description' => $request->description,
'min_stock' => $request->min_stock,
'display_price' => $request->display_price,
'discount' => $request->discount,
'price_poin' => $request->price_poin,
'bonus_poin' => $request->bonus_poin,
]);
$profile->prices()->delete();
$prices = collect($request->prices);
if ($prices->count() > 0) {
foreach ($prices as $price) {
$profile->prices()->create([
'customer_level_id' => $price['customer_level_id'],
'display_price' => $price['display_price'],
'discount' => $price['discount'],
'price_poin' => $price['price_poin'],
'bonus_poin' => $price['bonus_poin'],
]);
}
}
DB::commit();
return redirect()->route('location-profile.index')
->with('message', ['type' => 'success', 'message' => 'Item has beed updated']);
}
public function destroy(LocationProfile $profile)
{
$profile->delete();
return redirect()->route('location-profile.index')
->with('message', ['type' => 'success', 'message' => 'Item has beed deleted']);
}
}

@ -2,8 +2,8 @@
namespace App\Http\Controllers;
use App\Models\PoinReward;
use App\Models\CustomerLevel;
use App\Models\PoinReward;
use Illuminate\Http\Request;
class PoinRewardController extends Controller

@ -2,10 +2,17 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class CashDepositLocation extends Model
{
use HasFactory;
protected $fillable = [
'name',
'address',
'phone',
'gmap_url',
'image',
'description',
'open_hour',
'close_hour',
'is_active',
];
}

@ -1,60 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Support\Carbon;
class poinHistory extends Model
{
protected $fillable = [
'debit',
'credit',
'description',
'customer_id',
'related_type',
'related_id',
];
protected $appends = [
'amount',
'format_human_created_at',
'format_created_at',
];
public function formatHumanCreatedAt(): Attribute
{
return Attribute::make(get: function () {
return Carbon::parse($this->created_at)->locale('id')->translatedFormat('d F Y');
});
}
public function formatCreatedAt(): Attribute
{
return Attribute::make(get: function () {
return Carbon::parse($this->created_at)->locale('id')->translatedFormat('d F Y H:i:s');
});
}
public function amount(): Attribute
{
return Attribute::make(get: function () {
if ($this->credit == 0) {
return number_format($this->debit, is_float($this->debit) ? 2 : 0, ',', '.');
}
return number_format($this->credit, is_float($this->credit) ? 2 : 0, ',', '.');
});
}
public function customer()
{
return $this->belongsTo(Customer::class);
}
public function update_customer_balance()
{
$customer = Customer::find($this->customer_id);
$customer->update(['poin_balance' => $customer->poin_balance + $this->debit - $this->credit]);
}
}

@ -126,7 +126,7 @@ class Customer extends Authenticatable
return ' - ';
}
return '+62' . $this->phone;
return '+62'.$this->phone;
});
}
@ -234,7 +234,7 @@ class Customer extends Authenticatable
$paylater = $this->paylaterHistories()->create([
'credit' => $cut,
'description' => $deposit->description . ' (Pengembalian)',
'description' => $deposit->description.' (Pengembalian)',
]);
$paylater->update_customer_paylater();

@ -2,10 +2,15 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class CustomerCart extends Model
{
use HasFactory;
protected $fillable = [
'customer_id',
'sale_id',
'entity_type',
'entity_id',
'price',
'quantity',
'additional_info_json',
];
}

@ -14,6 +14,13 @@ class CustomerLevel extends Model
const MUST_VERIFIED = [self::GOLD, self::PLATINUM];
const LEVELS = [
self::BASIC,
self::SILVER,
self::GOLD,
self::PLATINUM,
];
protected $fillable = [
'name',
'description',
@ -22,4 +29,9 @@ class CustomerLevel extends Model
'max_amount',
'max_loan',
];
public static function getByKey($key)
{
return CustomerLevel::where('key', $key)->first();
}
}

@ -2,10 +2,10 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class CustomerLocationFavorite extends Model
{
use HasFactory;
protected $fillable = [
'customer_id',
'location_id',
];
}

@ -2,10 +2,78 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\Attribute;
class LocationProfile extends Model
{
use HasFactory;
const EXPIRED_HOUR = 'Jam';
const EXPIRED_DAY = 'Hari';
const EXPIRED_WEEK = 'Minggu';
const EXPIRED_MONTH = 'Bulan';
const EXPIRED_UNIT = [
self::EXPIRED_HOUR,
self::EXPIRED_DAY,
self::EXPIRED_WEEK,
self::EXPIRED_MONTH,
];
protected $fillable = [
'location_id',
'name',
'quota',
'display_note',
'expired',
'expired_unit',
'description',
'min_stock',
'price',
'display_price',
'discount',
'price_poin',
'bonus_poin',
];
protected $appends = [
'diplay_expired',
];
protected static function booted(): void
{
static::creating(function (LocationProfile $model) {
$price = $model->display_price;
if ($model->discount > 0) {
$price = $price - ($price * ($model->discount / 100));
}
$model->price = $price;
});
static::updating(function (LocationProfile $model) {
$price = $model->display_price;
if ($model->discount > 0) {
$price = $price - ($price * ($model->discount / 100));
}
$model->price = $price;
});
}
public function prices()
{
return $this->hasMany(LocationProfilePrice::class);
}
public function location()
{
return $this->belongsTo(Location::class);
}
public function diplayExpired(): Attribute
{
return Attribute::make(get: function () {
return $this->expired . ' ' . $this->expired_unit;
});
}
}

@ -2,10 +2,39 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class LocationProfilePrice extends Model
{
use HasFactory;
protected $fillable = [
'location_profile_id',
'customer_level_id',
'price',
'display_price',
'discount',
'price_poin',
'bonus_poin',
];
protected static function booted(): void
{
static::creating(function (LocationProfilePrice $model) {
$price = $model->display_price;
if ($model->discount > 0) {
$price = $price - ($price * ($model->discount / 100));
}
$model->price = $price;
});
static::updating(function (LocationProfilePrice $model) {
$price = $model->display_price;
if ($model->discount > 0) {
$price = $price - ($price * ($model->discount / 100));
}
$model->price = $price;
});
}
public function level()
{
return $this->belongsTo(CustomerLevel::class, 'customer_level_id');
}
}

@ -63,7 +63,7 @@ class Sale extends Model
public function displayAmount(): Attribute
{
return Attribute::make(get: function () {
return 'Rp' . number_format($this->amount, is_float($this->amount) ? 2 : 0, ',', '.');
return 'Rp'.number_format($this->amount, is_float($this->amount) ? 2 : 0, ',', '.');
});
}
@ -72,12 +72,12 @@ class Sale extends Model
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',
'description' => $this->customer->fullname.' melakukan penukaran '.$this->items()->count().' voucher sebesar '.$this->items->value('price').' poin',
]);
Notification::create([
'entity_id' => auth()->id(),
'description' => 'Transaksi ' . $this->code . ' berhasil',
'description' => 'Transaksi '.$this->code.' berhasil',
]);
return;
@ -85,12 +85,12 @@ class Sale extends Model
Notification::create([
'entity_type' => User::class,
'description' => $this->customer->fullname . ' melakukan pembelian ' . $this->items()->count() . ' voucher sebesar ' . $this->display_amount,
'description' => $this->customer->fullname.' melakukan pembelian '.$this->items()->count().' voucher sebesar '.$this->display_amount,
]);
Notification::create([
'entity_id' => auth()->id(),
'description' => 'Transaksi pembelian anda #' . $this->code . ' sebesar ' . $this->display_amount . ' berhasil',
'description' => 'Transaksi pembelian anda #'.$this->code.' sebesar '.$this->display_amount.' berhasil',
]);
}
}

@ -2,9 +2,6 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Support\Str;
class Voucher extends Model
{
const UNSOLD = 0;
@ -57,7 +54,7 @@ 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,
]);
}
}

@ -14,7 +14,7 @@ return new class extends Migration
Schema::create('infos', function (Blueprint $table) {
$table->ulid('id')->primary();
$table->string('title');
$table->text('title');
$table->string('destination')->nullable();
$table->string('type')->nullable();
$table->smallInteger('is_publish')->nullable();

@ -4,13 +4,14 @@ namespace Database\Seeders;
use App\Models\Account;
use App\Models\Banner;
use App\Models\CustomerLevel;
use App\Models\Info;
use App\Models\Location;
use App\Models\LocationProfile;
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
{
@ -25,6 +26,7 @@ class DummySeeder extends Seeder
$this->banner();
$this->account();
$this->location();
$this->location_profile();
$this->voucher();
}
@ -77,6 +79,66 @@ class DummySeeder extends Seeder
}
}
public function location_profile()
{
$profiles = [
LocationProfile::EXPIRED_DAY => '1 GB',
LocationProfile::EXPIRED_WEEK => '2 GB',
LocationProfile::EXPIRED_MONTH => '99 GB',
];
$count = 0;
$locations = Location::limit(3)->get();
foreach ($locations as $location) { //ada 3 lokasi di tiap lokasi ada 3 profile
$count += 1;
foreach ($profiles as $expired => $quota) {
$disply_price = 10000;
$discount = $expired == LocationProfile::EXPIRED_DAY ? 0 : 10;
$price = $disply_price - ($disply_price * ($discount / 100));
$lp = LocationProfile::create([
'location_id' => $location->id,
'name' => $quota,
'quota' => $quota,
'display_note' => 'bisa semua',
'expired' => rand(1, 3),
'expired_unit' => $expired,
'description' => '',
'min_stock' => 10,
'price' => $price,
'display_price' => $disply_price,
'discount' => $discount,
'price_poin' => $price,
'bonus_poin' => 0,
]);
if ($count == 3) {
$dp = 100000;
$disc = 0;
$bp = 0;
foreach (CustomerLevel::LEVELS as $index => $level) {
if ($index != 0) {
$disc += 5;
}
$p = $dp - ($dp * ($disc / 100));
$lp->prices()->create([
'customer_level_id' => CustomerLevel::getByKey($level)->id,
'price' => $p,
'display_price' => $dp,
'discount' => $disc,
'price_poin' => $p,
'bonus_poin' => $bp,
]);
}
}
}
}
}
public function voucher()
{
@ -84,25 +146,15 @@ class DummySeeder extends Seeder
DB::beginTransaction();
foreach ([1, 2, 3] as $loop) {
$batchId = Str::ulid();
$location = Location::get()[$loop];
$price_poin = $loop == 3 ? 10 : 0;
$profile = LocationProfile::get()[$loop];
foreach ($vouchers as $voucher) {
Voucher::create([
'location_id' => $location->id,
'location_profile_id' => $profile->id,
'username' => $voucher['username'],
'password' => $voucher['password'],
'discount' => $loop == 1 ? 10 : 0,
'display_price' => $loop == 1 ? 100000 : 99000,
'price_poin' => $price_poin,
'quota' => $voucher['quota'],
'profile' => $voucher['profile'],
'comment' => $voucher['comment'],
'expired' => 30,
'expired_unit' => 'Hari',
'batch_id' => $batchId,
]);
}
}

@ -37,10 +37,10 @@ class InstallationSeed extends Seeder
['key' => 'ENABLE_CASH_DEPOSIT', 'value' => '0', 'type' => 'text'],
['key' => 'ENABLE_MANUAL_TRANSFER', 'value' => '0', 'type' => 'text'],
['key' => 'MAX_MANUAL_TRANSFER_TIMEOUT', 'value' => '2', 'type' => 'text'], //dalam jam
['key' => 'MAX_MANUAL_TRANSFER_TIMEOUT', 'value' => '2', 'type' => 'text'], //dalam jam
['key' => 'MANUAL_TRANSFER_OPEN_HOUR', 'value' => '06:00', 'type' => 'text'],
['key' => 'MANUAL_TRANSFER_CLOSE_HOUR', 'value' => '23:00', 'type' => 'text'],
['key' => 'MAX_POINT_EXPIRED', 'value' => '90', 'type' => 'text'], //dalam hari
['key' => 'MAX_POINT_EXPIRED', 'value' => '90', 'type' => 'text'], //dalam hari
];
foreach ($settings as $setting) {

@ -83,6 +83,11 @@ class PermissionSeeder extends Seeder
['id' => Str::ulid(), 'label' => 'Update Lokasi', 'name' => 'update-location'],
['id' => Str::ulid(), 'label' => 'View Lokasi', 'name' => 'view-location'],
['id' => Str::ulid(), 'label' => 'Delete Lokasi', 'name' => 'delete-location'],
['id' => Str::ulid(), 'label' => 'Create Profile Lokasi', 'name' => 'create-location-profile'],
['id' => Str::ulid(), 'label' => 'Update Profile Lokasi', 'name' => 'update-location-profile'],
['id' => Str::ulid(), 'label' => 'View Profile Lokasi', 'name' => 'view-location-profile'],
['id' => Str::ulid(), 'label' => 'Delete Profile Lokasi', 'name' => 'delete-location-profile'],
];
foreach ($permissions as $permission) {

@ -67,8 +67,18 @@ services:
networks:
voucher:
ipv4_address: 10.25.10.99
portainer:
image: portainer/portainer-ce:latest
ports:
- 9443:9443
volumes:
- data:/data
- /var/run/docker.sock:/var/run/docker.sock
restart: unless-stopped
volumes:
data:
driver: local
mariadb:
driver: local

@ -1,32 +1,58 @@
import React from "react";
import { NumericFormat } from "react-number-format";
import Input from "./Input";
import React from 'react'
import { NumericFormat } from 'react-number-format'
import Input from './Input'
import { toFixed } from '@/utils'
export default function FormInputNumeric({
name,
onChange,
value,
label,
className,
error,
max = 999999999,
fixed = true,
}) {
if (fixed) {
value = toFixed(value)
}
value = Number(value)
export default function FormInputNumeric({ name, onChange, value, label, className, error }) {
return (
<div className={className}>
<label htmlFor="first_name" className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{label}</label>
<div>
<label
htmlFor="first_name"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>
{label}
</label>
<NumericFormat
thousandSeparator=","
decimalSeparator="."
max={max}
thousandSeparator="."
decimalSeparator=","
allowNegative={false}
allowLeadingZeros={false}
className={className}
className={`${className} ${
error &&
'focus:ring-red-600 border-red-600 focus:border-red-600'
}`}
customInput={Input}
value={value}
name={name}
onValueChange={(values) => {
onChange({
onChange({
target: {
name: name,
value: values.floatValue
}
value: values.floatValue,
},
})
}}
}}
/>
{error && (
<p className="mb-2 text-sm text-red-600 dark:text-red-500">{error}</p>
<p className="mb-2 text-sm text-red-600 dark:text-red-500">
{error}
</p>
)}
</div>
)
}
}

@ -7,6 +7,7 @@ import {
HiCog,
} from 'react-icons/hi'
import {
HiArchiveBox,
HiBanknotes,
HiCheckBadge,
HiCreditCard,
@ -39,10 +40,10 @@ export default [
{
name: 'Profile Lokasi',
show: true,
icon: HiMap,
route: route('location.index'),
active: 'location.*',
permission: 'view-location',
icon: HiArchiveBox,
route: route('location-profile.index'),
active: 'location-profile.*',
permission: 'view-location-profile',
},
{
name: 'Voucher',

@ -9,7 +9,7 @@ import {
ArcElement,
Legend,
} from 'chart.js'
import { Bar, Doughnut } from 'react-chartjs-2'
import { Bar } from 'react-chartjs-2'
import { Head, router } from '@inertiajs/react'
import { usePrevious } from 'react-use'
import moment from 'moment'
@ -105,72 +105,6 @@ export default function Dashboard(props) {
<div>
<div className="mx-auto sm:px-6 lg:px-8 ">
<div className="grid grid-cols-2 lg:grid-cols-4 gap-2">
<div className="border rounded-md shadow bg-white p-4 flex flex-col">
<div className="text-gray-600 text-xl">
Total Voucher
</div>
<div className="font-bold text-2xl pt-2">
{formatIDR(total_voucher)}
</div>
</div>
<div className="border rounded-md shadow bg-white p-4 flex flex-col">
<div className="text-gray-600 text-xl">
Total Customer
</div>
<div className="font-bold text-2xl pt-2">
{formatIDR(total_customer)}
</div>
</div>
<div className="border rounded-md shadow bg-white p-4 flex flex-col">
<div className="text-gray-600 text-xl">
Total Customer Verified
</div>
<div className="font-bold text-2xl pt-2">
{formatIDR(total_customer_verified)}
</div>
</div>
<div className="border rounded-md shadow bg-white p-4 flex flex-col">
<div className="text-gray-600 text-xl">
Total Deposit
</div>
<div className="font-bold text-2xl pt-2">
{formatIDR(total_deposit)}
</div>
</div>
<div className="border rounded-md shadow bg-white p-4 flex flex-col">
<div className="text-gray-600 text-xl">
Total Voucher terjual bulan {month}
</div>
<div className="font-bold text-2xl pt-2">
{formatIDR(total_voucher_sale_this_month)}
</div>
</div>
<div className="border rounded-md shadow bg-white p-4 flex flex-col">
<div className="text-gray-600 text-xl">
Jumlah Voucher terjual bulan {month}
</div>
<div className="font-bold text-2xl pt-2">
{formatIDR(count_voucher_sale_this_month)}
</div>
</div>
<div className="border rounded-md shadow bg-white p-4 flex flex-col">
<div className="text-gray-600 text-xl">
Total Voucher terjual hari ini
</div>
<div className="font-bold text-2xl pt-2">
{formatIDR(total_voucher_sale_this_day)}
</div>
</div>
<div className="border rounded-md shadow bg-white p-4 flex flex-col">
<div className="text-gray-600 text-xl">
Jumlah Voucher terjual hari ini
</div>
<div className="font-bold text-2xl pt-2">
{formatIDR(count_voucher_sale_this_day)}
</div>
</div>
</div>
<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-col md:flex-row justify-between mb-4">

@ -67,14 +67,14 @@ export default function FormModal(props) {
name="name"
value={data.name}
onChange={handleOnChange}
label="name"
label="Nama"
error={errors.name}
/>
<FormInput
name="description"
value={data.description}
onChange={handleOnChange}
label="description"
label="Deskripsi"
error={errors.description}
/>

@ -74,13 +74,13 @@ export default function Index(props) {
scope="col"
className="py-3 px-6"
>
Name
Nama
</th>
<th
scope="col"
className="py-3 px-6"
>
Description
Deskripsi
</th>
<th
scope="col"

@ -0,0 +1,353 @@
import React, { useEffect, useState } from 'react'
import { isEmpty } from 'lodash'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
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 Checkbox from '@/Components/Checkbox'
import { DEFAULT_EXPIRED_UNIT } from '@/constant'
import { toFixed } from '@/utils'
import FormInputNumeric from '@/Components/FormInputNumeric'
export default function Form(props) {
const { profile, levels, expireds } = props
const [use_level, setUseLevel] = useState(false)
const { data, setData, post, processing, errors } = useForm({
location_id: null,
name: '',
quota: '',
display_note: '',
expired: '',
expired_unit: DEFAULT_EXPIRED_UNIT,
description: '',
min_stock: 0,
display_price: 0,
discount: 0,
price_poin: 0,
bonus_poin: 0,
prices: null,
})
const handleOnChange = (event) => {
setData(
event.target.name,
event.target.type === 'checkbox'
? event.target.checked
? 1
: 0
: event.target.value
)
}
const handleUseLevel = () => {
setUseLevel(!use_level)
if (!use_level === true) {
const prices = levels.map((level) => {
return {
level: level,
customer_level_id: level.id,
display_price: 0,
discount: 0,
price_poin: 0,
bonus_poin: 0,
}
})
setData('prices', prices)
return
}
setData('prices', null)
}
const handleSetLevelPrice = (id, event) => {
setData(
'prices',
data.prices.map((price) => {
if (price.customer_level_id === id) {
price[event.target.name] = event.target.value
}
return price
})
)
}
const handleSubmit = () => {
if (isEmpty(profile) === false) {
post(route('location-profile.update', profile))
return
}
post(route('location-profile.store'))
}
useEffect(() => {
if (isEmpty(profile) === false) {
if (profile.prices.length > 0) {
setUseLevel(true)
}
setData({
name: profile.name,
quota: profile.quota,
display_note: profile.display_note,
description: profile.description,
min_stock: profile.min_stock,
display_price: profile.display_price,
discount: profile.discount,
price_poin: profile.price_poin,
bonus_poin: profile.bonus_poin,
expired: profile.expired,
expired_unit: profile.expired_unit,
location_id: profile.location_id,
prices: profile.prices.map((price) => {
return {
...price,
display_price: price.display_price,
discount: price.discount,
price_poin: price.price_poin,
bonus_poin: price.bonus_poin,
}
}),
})
}
}, [profile])
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Profile Lokasi'}
action={'Form'}
>
<Head title="Profile Lokasi" />
<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">
Profile Lokasi
</div>
<LocationSelectionInput
label="Lokasi"
itemSelected={data.location_id}
onItemSelected={(id) => setData('location_id', id)}
error={errors.location_id}
/>
<div className="mt-2" />
<FormInput
name="name"
value={data.name}
onChange={handleOnChange}
label="Nama"
error={errors.name}
/>
<FormInput
name="display_note"
value={data.display_note}
onChange={handleOnChange}
label="Catatan"
error={errors.display_note}
/>
<FormInputNumeric
name="display_price"
value={data.display_price}
onChange={handleOnChange}
label="Harga"
error={errors.display_price}
fixed={false}
/>
<FormInputWith
type="number"
rightItem={<div className="text-sm">%</div>}
name="discount"
value={data.discount}
onChange={handleOnChange}
error={errors.discount}
formClassName={'pr-10'}
label="Discount"
/>
<FormInputNumeric
name="price_poin"
value={data.price_poin}
onChange={handleOnChange}
label="Harga poin (untuk penukaran)"
error={errors.price_poin}
/>
<FormInputNumeric
name="bonus_poin"
value={data.bonus_poin}
onChange={handleOnChange}
label="Bonus poin"
error={errors.bonus_poin}
/>
<FormInput
name="quota"
value={data.quota}
onChange={handleOnChange}
label="Kuota"
error={errors.quota}
/>
<FormInputNumeric
name="min_stock"
value={data.min_stock}
onChange={handleOnChange}
label="Minimal Stok"
error={errors.min_stock}
/>
<div className="mb-2">
<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}
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"
>
{expireds.map((unit) => (
<option value={unit} key={unit}>
{unit}
</option>
))}
</select>
</div>
</div>
</div>
<Checkbox
label="Level Harga"
value={use_level}
onChange={(e) => handleUseLevel(e.target.value)}
/>
<div
className={`p-2 mt-2 border rounded ${
!use_level && 'hidden'
}`}
>
{errors.prices && (
<p className="mb-2 text-sm text-red-600 dark:text-red-500">
{errors.prices}
</p>
)}
<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">
Level
</th>
<th scope="col" className="py-3 px-6">
Harga
</th>
<th scope="col" className="py-3 px-6">
Diskon
</th>
<th scope="col" className="py-3 px-6">
Harga Poin
</th>
<th scope="col" className="py-3 px-6">
Bonus Poin
</th>
</tr>
</thead>
<tbody>
{data.prices?.map((price) => (
<tr
className="bg-white border-b dark:bg-gray-800 dark:border-gray-700"
key={price.level.id}
>
<td
scope="row"
className="py-4 px-6 font-medium text-gray-900 whitespace-nowrap dark:text-white"
>
{price.level.name}
</td>
<td
scope="row"
className="py-2 px-2"
>
<FormInputNumeric
value={price.display_price}
name="display_price"
onChange={(e) =>
handleSetLevelPrice(
price.customer_level_id,
e
)
}
/>
</td>
<td
scope="row"
className="py-2 px-2"
>
<FormInputNumeric
value={price.discount}
name="discount"
onChange={(e) =>
handleSetLevelPrice(
price.customer_level_id,
e
)
}
/>
</td>
<td
scope="row"
className="py-2 px-2"
>
<FormInputNumeric
value={price.price_poin}
name="price_poin"
onChange={(e) =>
handleSetLevelPrice(
price.customer_level_id,
e
)
}
/>
</td>
<td
scope="row"
className="py-2 px-2"
>
<FormInputNumeric
value={price.bonus_poin}
name="bonus_poin"
onChange={(e) =>
handleSetLevelPrice(
price.customer_level_id,
e
)
}
/>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="mt-8">
<Button
onClick={handleSubmit}
processing={processing}
>
Simpan
</Button>
</div>
</div>
</div>
</div>
</AuthenticatedLayout>
)
}

@ -0,0 +1,223 @@
import React, { useEffect, useState } from 'react'
import { Link, router } from '@inertiajs/react'
import { usePrevious } from 'react-use'
import { Head } from '@inertiajs/react'
import { Button, Dropdown } from 'flowbite-react'
import { HiPencil, HiTrash } from 'react-icons/hi'
import { useModalState } from '@/hooks'
import 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,
} = props
const [location, setLocation] = useState(null)
const [search, setSearch] = useState('')
const preValue = usePrevious(`${search}${location}`)
const confirmModal = useModalState()
const handleDeleteClick = (profile) => {
confirmModal.setData(profile)
confirmModal.toggle()
}
const onDelete = () => {
if (confirmModal.data !== null) {
router.delete(
route('location-profile.destroy', confirmModal.data.id)
)
}
}
const params = { q: search, location_id: location }
useEffect(() => {
if (preValue) {
router.get(
route(route().current()),
{ q: search, location_id: location },
{
replace: true,
preserveState: true,
}
)
}
}, [search, location])
const canCreate = hasPermission(auth, 'create-location-profile')
const canUpdate = hasPermission(auth, 'update-location-profile')
const canDelete = hasPermission(auth, 'delete-location-profile')
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
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">
{canCreate && (
<div className="flex flex-row space-x-2">
<Link
href={route('location-profile.create')}
>
<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>
</div>
<div className="overflow-auto">
<div>
<table className="w-full text-sm text-left text-gray-500 dark:text-gray-400 mb-4">
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th
scope="col"
className="py-3 px-6"
>
Nama
</th>
<th
scope="col"
className="py-3 px-6"
>
Lokasi
</th>
<th
scope="col"
className="py-3 px-6"
>
Kuota
</th>
<th
scope="col"
className="py-3 px-6"
>
Masa Aktif
</th>
<th
scope="col"
className="py-3 px-6 w-1/8"
/>
</tr>
</thead>
<tbody>
{data.map((profile, index) => (
<tr
className="bg-white border-b dark:bg-gray-800 dark:border-gray-700"
key={profile.id}
>
<td
scope="row"
className="py-4 px-6 font-medium text-gray-900 whitespace-nowrap dark:text-white"
>
{profile.name}
</td>
<td
scope="row"
className="py-4 px-6"
>
{profile.location.name}
</td>
<td
scope="row"
className="py-4 px-6"
>
{profile.quota}
</td>
<td
scope="row"
className="py-4 px-6"
>
{profile.diplay_expired}
</td>
<td className="py-4 px-6 flex justify-end">
<Dropdown
label={'Opsi'}
floatingArrow={true}
arrowIcon={true}
dismissOnClick={true}
size={'sm'}
>
{canUpdate && (
<Dropdown.Item>
<Link
href={route(
'location-profile.edit',
profile
)}
className="flex space-x-1 items-center"
>
<HiPencil />
<div>
Ubah
</div>
</Link>
</Dropdown.Item>
)}
{canDelete && (
<Dropdown.Item
onClick={() =>
handleDeleteClick(
profile
)
}
>
<div className="flex space-x-1 items-center">
<HiTrash />
<div>
Hapus
</div>
</div>
</Dropdown.Item>
)}
</Dropdown>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="w-full flex items-center justify-center">
<Pagination links={links} params={params} />
</div>
</div>
</div>
</div>
</div>
<ModalConfirm modalState={confirmModal} onConfirm={onDelete} />
</AuthenticatedLayout>
)
}

@ -0,0 +1 @@
export const DEFAULT_EXPIRED_UNIT = 'Hari'

@ -51,7 +51,7 @@ export const formatIDDate = (date) => {
export const hasPermission = (auth, permission) => {
const { user } = auth
if (+user.is_superadmin === 1) {
if (user.role === null) {
return true
}
@ -62,3 +62,7 @@ export const hasPermission = (auth, permission) => {
}
return false
}
export const toFixed = (num) => {
return Number(num).toFixed()
}

@ -3,14 +3,15 @@
use App\Http\Controllers\AccountController;
use App\Http\Controllers\Auth\AuthenticatedSessionController;
use App\Http\Controllers\BannerController;
use App\Http\Controllers\PoinRewardController;
use App\Http\Controllers\CustomerController;
use App\Http\Controllers\CustomerLevelController;
use App\Http\Controllers\DepositController;
use App\Http\Controllers\GeneralController;
use App\Http\Controllers\InfoController;
use App\Http\Controllers\LocationController;
use App\Http\Controllers\LocationProfileController;
use App\Http\Controllers\NotificationController;
use App\Http\Controllers\PoinRewardController;
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\RoleController;
use App\Http\Controllers\SaleController;
@ -66,6 +67,14 @@ Route::middleware(['http_secure_aware', 'inertia.admin'])
Route::put('/locations/{location}', [LocationController::class, 'update'])->name('location.update');
Route::delete('/locations/{location}', [LocationController::class, 'destroy'])->name('location.destroy');
// Profile Location
Route::get('/location-profile', [LocationProfileController::class, 'index'])->name('location-profile.index');
Route::get('/location-profile/create', [LocationProfileController::class, 'create'])->name('location-profile.create');
Route::post('/location-profile', [LocationProfileController::class, 'store'])->name('location-profile.store');
Route::get('/location-profile/{profile}', [LocationProfileController::class, 'edit'])->name('location-profile.edit');
Route::post('/location-profile/{profile}', [LocationProfileController::class, 'update'])->name('location-profile.update');
Route::delete('/location-profile/{profile}', [LocationProfileController::class, 'destroy'])->name('location-profile.destroy');
// Account
Route::get('/accounts', [AccountController::class, 'index'])->name('account.index');
Route::post('/accounts', [AccountController::class, 'store'])->name('account.store');

@ -2,11 +2,11 @@
use App\Http\Controllers\Customer\AuthController;
use App\Http\Controllers\Customer\CartController;
use App\Http\Controllers\Customer\PoinController;
use App\Http\Controllers\Customer\PoinExchangeController;
use App\Http\Controllers\Customer\DepositController;
use App\Http\Controllers\Customer\HomeController;
use App\Http\Controllers\Customer\PaylaterController;
use App\Http\Controllers\Customer\PoinController;
use App\Http\Controllers\Customer\PoinExchangeController;
use App\Http\Controllers\Customer\ProfileController;
use App\Http\Controllers\Customer\TransactionController;
use App\Http\Controllers\Customer\VerificationController;

Loading…
Cancel
Save