migrate to postgresql, and check functional

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

@ -3,7 +3,7 @@
## Note
[x] Kerjakan dulu semua yang terkait penjualan normal, deposit normal dan bonus poin transaksi dan downline
[ ] setelah itu baru penukaran voucher
[ ] setelah itu baru penukaran voucher -> sedang ngerjain ini
[ ] baru setelah itu yang berhubungan dengan mitrawbb dan hutang
## Front

@ -36,7 +36,7 @@ class GeneralController extends Controller
$deposites = DepositHistory::whereDate('created_at', now()->format('Y-m-d'))
->where('is_valid', DepositHistory::STATUS_VALID)
->where('debit', '!=', '0')
->groupBy('customer_id')
->groupBy('is_valid', 'customer_id')
->orderBy('total', 'desc')
->with('customer')
->limit(20)
@ -46,7 +46,7 @@ class GeneralController extends Controller
$sales = SaleItem::whereDate('sale_items.created_at', now()->format('Y-m-d'))
->join('sales', 'sales.id', '=', 'sale_items.sale_id')
->join('customers', 'customers.id', '=', 'sales.customer_id')
->groupBy('sales.customer_id')
->groupBy('sales.customer_id', 'customers.name', 'entity_id')
->orderBy('total', 'desc')
->limit(20)
->selectRaw('sum(sale_items.price) as total, sum(quantity) as count, sales.customer_id, customers.name, entity_id')

@ -10,7 +10,7 @@ class SaleController extends Controller
{
public function index(Request $request)
{
$query = Sale::with(['items.voucher', 'customer.level'])
$query = Sale::with(['items', 'customer.level'])
->withCount(['items'])
->orderBy('updated_at', 'desc');
@ -31,7 +31,7 @@ class SaleController extends Controller
public function show(Sale $sale)
{
return inertia('Sale/Detail', [
'sale' => $sale->load(['items.voucher.location', 'customer.level']),
'sale' => $sale->load(['items', 'customer.level']),
]);
}
}

@ -5,6 +5,7 @@ namespace App\Http\Controllers\Customer;
use App\Http\Controllers\Controller;
use App\Models\Customer;
use App\Models\DepositHistory;
use App\Models\LocationProfile;
use App\Models\PoinReward;
use App\Models\Sale;
use App\Models\Setting;
@ -25,9 +26,9 @@ class CartController extends Controller
public function index(Request $request)
{
$customer = $request->user('customer');
$carts = $customer->carts->load(['voucher.locationProfile.location']);
$carts = $customer->carts->load(['locationProfile.location']);
$total = $carts->sum(function ($item) {
return $item->quantity * $item->voucher->validate_price;
return $item->quantity * $item->locationProfile->validate_price;
});
$checkAllowProcess = [
@ -48,12 +49,12 @@ class CartController extends Controller
/**
* handle cart add, remove or sub
*/
public function store(Request $request, Voucher $voucher)
public function store(Request $request, LocationProfile $profile)
{
$operator = $request->param ?? 'add'; //delete, sub, add
$customer = $request->user('customer');
$item = $customer->carts()->where(['entity_id' => $voucher->id])->first();
$item = $customer->carts()->where(['entity_id' => $profile->id])->first();
if ($item !== null) {
if ($operator == 'delete') {
$item->delete();
@ -74,7 +75,7 @@ class CartController extends Controller
}
} else {
$customer->carts()->create([
'entity_id' => $voucher->id,
'entity_id' => $profile->id,
'quantity' => 1
]);
@ -102,7 +103,7 @@ class CartController extends Controller
]);
$customer = $request->user('customer');
$carts = $customer->carts->load(['voucher.locationProfile.location']);
$carts = $customer->carts->load(['locationProfile.location']);
// validate carts not empty
if ($carts->count() == 0) {
@ -112,7 +113,7 @@ class CartController extends Controller
// validate voucher is available
foreach ($carts as $item) {
$batchCount = $item->voucher->count_unsold();
$batchCount = $item->locationProfile->count_unsold();
if ($batchCount < $item->quantity) {
$customer->carts()->delete();
@ -123,7 +124,7 @@ class CartController extends Controller
// calculate total
$total = $carts->sum(function ($item) {
return $item->quantity * $item->voucher->validate_price;
return $item->quantity * $item->locationProfile->validate_price;
});
DB::beginTransaction();
@ -136,7 +137,7 @@ class CartController extends Controller
// create sale item by per voucher
foreach ($carts as $item) {
$vouchers = $item->voucher->shuffle_unsold($item->quantity);
$vouchers = $item->locationProfile->shuffle_unsold($item->quantity);
foreach ($vouchers as $voucher) {
$sale->items()->create([
'entity_type' => $voucher::class,
@ -149,7 +150,7 @@ class CartController extends Controller
]);
$voucher->update(['is_sold' => Voucher::SOLD]);
$voucher->check_stock_notification();
$voucher->create_bonus_poin($customer);

@ -8,6 +8,7 @@ use App\Models\Customer;
use App\Models\CustomerLocationFavorite;
use App\Models\Info;
use App\Models\Location;
use App\Models\LocationProfile;
use App\Models\Notification;
use App\Models\Voucher;
use Illuminate\Http\Request;
@ -31,30 +32,29 @@ class HomeController extends Controller
$slocations = [];
$vouchers = Voucher::with(['locationProfile.location'])
->where('is_sold', Voucher::UNSOLD)
->orderBy('updated_at', 'desc')
->groupBy('location_profile_id');
$profiles = LocationProfile::with(['location'])
->whereHas('vouchers', function ($q) {
$q->where('is_sold', Voucher::UNSOLD);
})
->orderBy('updated_at', 'desc');
if ($request->location_ids != '') {
$vouchers->whereHas('locationProfile', function ($q) use ($request) {
return $q->whereIn('location_id', $request->location_ids);
});
$profiles->whereIn('location_id', $request->location_ids);
$slocations = Location::whereIn('id', $request->location_ids)->get();
$vouchers = tap($vouchers->paginate(self::LIMIT))->setHidden(['username', 'password']);
$profiles = $profiles->paginate(self::LIMIT);
}
if (auth()->guard('customer')->guest() && $request->location_ids == '') {
$vouchers = tap($vouchers->paginate(self::LIMIT))->setHidden(['username', 'password']);
$profiles = $profiles->paginate(self::LIMIT);
}
return inertia('Index/Index', [
'infos' => $infos,
'banners' => $banners,
'locations' => $locations,
'vouchers' => $vouchers,
'profiles' => $profiles,
'_slocations' => $slocations,
'_status' => 0
]);
@ -66,21 +66,20 @@ class HomeController extends Controller
$banners = Banner::orderBy('updated_at', 'desc')->get();
$locations = Location::orderBy('name', 'asc')->get();
$vouchers = Voucher::with(['locationProfile.location'])
->where('is_sold', Voucher::UNSOLD)
->orderBy('updated_at', 'desc')
->groupBy('location_profile_id');
$profiles = LocationProfile::with(['location'])
->whereHas('vouchers', function ($q) {
$q->where('is_sold', Voucher::UNSOLD);
})
->orderBy('updated_at', 'desc');
$customer = Customer::find(auth()->id());
$vouchers->whereHas('locationProfile', function ($q) use ($customer) {
return $q->whereIn('location_id', $customer->locationFavorites()->pluck('id')->toArray());
});
$profiles->whereIn('location_id', $customer->locationFavorites()->pluck('id')->toArray());
return inertia('Index/Index', [
'infos' => $infos,
'banners' => $banners,
'locations' => $locations,
'vouchers' => tap($vouchers->paginate(self::LIMIT))->setHidden(['username', 'password']),
'profiles' => $profiles->paginate(self::LIMIT),
'_flocations' => $customer->locationFavorites,
'_status' => 1
]);

@ -14,20 +14,24 @@ class PoinExchangeController extends Controller
{
public function index(Request $request)
{
$favorite = $request->favorite ?? 1;
$customer = $request->user('customer');
$flocations = $customer->locationFavorites;
$slocations = [];
$locations = Location::orderBy('name', 'asc')->get();
$vouchers = Voucher::with(['locationProfile.location'])
->whereHas('locationProfile', function ($q) {
$q->where(['price_poin', '!=', 0])
$q->where('price_poin', '!=', 0)
->orWhereHas('prices', function ($q) {
return $q->where(['price_poin', '!=', 0]);
return $q->where('price_poin', '!=', 0);
});
})
->where('is_sold', Voucher::UNSOLD)
->groupBy('location_profile_id')
->orderBy('updated_at', 'desc');
if ($request->location_id != '') {
if ($request->location_ids != '') {
$vouchers->whereHas('locationProfile', function ($q) use ($request) {
return $q->whereIn('location_id', $request->location_ids);
});
@ -37,15 +41,20 @@ class PoinExchangeController extends Controller
$vouchers = tap($vouchers->paginate(20))->setHidden(['username', 'password']);
}
if (auth()->guard('customer')->guest() && $request->location_ids == '') {
if ($request->location_ids == '' && $flocations->count() > 0) {
$favorite = 1;
$vouchers->whereHas('locationProfile', function ($q) use ($flocations) {
return $q->whereIn('location_id', $flocations->pluck('id')->toArray());
});
$vouchers = tap($vouchers->paginate(20))->setHidden(['username', 'password']);
}
return inertia('Poin/Exchange', [
'locations' => $locations,
'vouchers' => $vouchers,
'_location_id' => $request->location_id ?? '',
'_slocations' => $slocations,
'_flocations' => $flocations,
'_favorite' => $favorite
]);
}
@ -54,7 +63,7 @@ class PoinExchangeController extends Controller
$batchCount = $voucher->count_unsold();
if ($batchCount < 1) {
return redirect()->route('customer.poin.exchange')
->with('message', ['type' => 'error', 'message'=> 'transaksi gagal, voucher sedang tidak tersedia']);
->with('message', ['type' => 'error', 'message' => 'transaksi gagal, voucher sedang tidak tersedia']);
}
$customer = Customer::find(auth()->id());

@ -18,4 +18,9 @@ class CustomerCart extends Model
{
return $this->belongsTo(Voucher::class, 'entity_id');
}
public function locationProfile()
{
return $this->belongsTo(LocationProfile::class, 'entity_id');
}
}

@ -38,7 +38,10 @@ class LocationProfile extends Model
];
protected $appends = [
'diplay_expired',
'display_expired',
'validate_price',
'validate_display_price',
'validate_discount',
];
protected static function booted(): void
@ -60,6 +63,19 @@ class LocationProfile extends Model
});
}
private static $instance = [];
private static function getInstance()
{
if (count(self::$instance) == 0) {
self::$instance = [
'customer' => Customer::find(auth()->guard('customer')->id())
];
}
return self::$instance;
}
public function prices()
{
return $this->hasMany(LocationProfilePrice::class);
@ -75,7 +91,7 @@ class LocationProfile extends Model
return $this->hasMany(Voucher::class);
}
public function diplayExpired(): Attribute
public function displayExpired(): Attribute
{
return Attribute::make(get: function () {
return $this->expired . ' ' . $this->expired_unit;
@ -93,4 +109,74 @@ class LocationProfile extends Model
];
});
}
public function validatePrice(): Attribute
{
return Attribute::make(get: function () {
if ($this->prices->count() > 0) {
$price = $this->prices;
if (auth()->guard('customer')->check()) {
$customer = self::getInstance()['customer'];
return $price->where('customer_level_id', $customer->customer_level_id)
->value('price');
}
return $price->max('price');
}
return $this->price;
});
}
public function validateDisplayPrice(): Attribute
{
return Attribute::make(get: function () {
if ($this->prices->count() > 0) {
$price = $this->prices;
if (auth()->guard('customer')->check()) {
$customer = self::getInstance()['customer'];
return $price->where('customer_level_id', $customer->customer_level_id)
->value('display_price');
}
return $price->max('display_price');
}
return $this->display_price;
});
}
public function validateDiscount(): Attribute
{
return Attribute::make(get: function () {
if ($this->prices->count() > 0) {
$price = $this->prices;
if (auth()->guard('customer')->check()) {
$customer = self::getInstance()['customer'];
return $price->where('customer_level_id', $customer->customer_level_id)
->value('discount');
}
return $price->min('discount');
}
return $this->discount;
});
}
public function shuffle_unsold($limit)
{
$vouchers = Voucher::where([
['is_sold', '=', Voucher::UNSOLD],
['location_profile_id', '=', $this->id],
])
->limit($limit)
->get();
return $vouchers;
}
public function count_unsold()
{
$voucher = Voucher::where([
['is_sold', '=', Voucher::UNSOLD],
['location_profile_id', '=', $this->id],
])->count();
return $voucher;
}
}

@ -149,28 +149,6 @@ class Voucher extends Model
});
}
public function shuffle_unsold($limit)
{
$vouchers = Voucher::where([
['is_sold', '=', self::UNSOLD],
['location_profile_id', '=', $this->location_profile_id],
])
->limit($limit)
->get();
return $vouchers;
}
public function count_unsold()
{
$voucher = Voucher::where([
['is_sold', '=', self::UNSOLD],
['location_profile_id', '=', $this->location_profile_id],
])->count();
return $voucher;
}
public function check_stock_notification()
{
$count = Voucher::where([
@ -192,10 +170,12 @@ class Voucher extends Model
{
$locationCallback = fn ($q) => $q->where('location_id', $location->id);
$count_voucher_total = Voucher::whereHas('locationProfile', $locationCallback)->count();
$sum_voucher_total = Voucher::whereHas('locationProfile', $locationCallback)
->join('location_profiles', 'location_profiles.id', '=', 'vouchers.location_profile_id')
->selectRaw('(count(vouchers.id) * location_profiles.price) as total')
->selectRaw('(sum(location_profiles.price)) as total')
->value('total');
$count_voucher_sold = Voucher::whereHas('locationProfile', $locationCallback)
->where('is_sold', Voucher::SOLD)->count();
$count_voucher_unsold = Voucher::whereHas('locationProfile', $locationCallback)
@ -203,7 +183,7 @@ class Voucher extends Model
$sum_voucher_unsold = Voucher::whereHas('locationProfile', $locationCallback)
->where('is_sold', Voucher::UNSOLD)
->join('location_profiles', 'location_profiles.id', '=', 'vouchers.location_profile_id')
->selectRaw('(count(vouchers.id) * location_profiles.price) as total')
->selectRaw('(sum(location_profiles.price)) as total')
->value('total');
$sum_voucher_sold = SaleItem::whereHas('voucher', function ($q) use ($locationCallback) {

@ -14,17 +14,17 @@
"laravel/socialite": "^5.6.3",
"laravel/tinker": "^2.8.1",
"midtrans/midtrans-php": "^2.5.2",
"react/async": "^4",
"react/async": "^4.1",
"socialiteproviders/google": "^4.1",
"tightenco/ziggy": "^1.6.0"
},
"require-dev": {
"beyondcode/laravel-dump-server": "^1.9",
"fakerphp/faker": "^1.23.0",
"laradumps/laradumps": "^1.12",
"laravel/breeze": "^1.21.0",
"laravel/pint": "^1.10.2",
"laravel/sail": "^1.22.0",
"laradumps/laradumps": "^1.12.3",
"laravel/breeze": "^1.21.1",
"laravel/pint": "^1.10.3",
"laravel/sail": "^1.23.0",
"mockery/mockery": "^1.6.2",
"nunomaduro/collision": "^6.4",
"phpunit/phpunit": "^9.6.9",

149
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": "1dc99b21d1aa2534f1a2a2e6f28ed566",
"content-hash": "9a5a4aa44ec60adc8aa5223f1cdf6e32",
"packages": [
{
"name": "brick/math",
@ -138,28 +138,28 @@
},
{
"name": "doctrine/inflector",
"version": "2.0.6",
"version": "2.0.8",
"source": {
"type": "git",
"url": "https://github.com/doctrine/inflector.git",
"reference": "d9d313a36c872fd6ee06d9a6cbcf713eaa40f024"
"reference": "f9301a5b2fb1216b2b08f02ba04dc45423db6bff"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/inflector/zipball/d9d313a36c872fd6ee06d9a6cbcf713eaa40f024",
"reference": "d9d313a36c872fd6ee06d9a6cbcf713eaa40f024",
"url": "https://api.github.com/repos/doctrine/inflector/zipball/f9301a5b2fb1216b2b08f02ba04dc45423db6bff",
"reference": "f9301a5b2fb1216b2b08f02ba04dc45423db6bff",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^10",
"doctrine/coding-standard": "^11.0",
"phpstan/phpstan": "^1.8",
"phpstan/phpstan-phpunit": "^1.1",
"phpstan/phpstan-strict-rules": "^1.3",
"phpunit/phpunit": "^8.5 || ^9.5",
"vimeo/psalm": "^4.25"
"vimeo/psalm": "^4.25 || ^5.4"
},
"type": "library",
"autoload": {
@ -209,7 +209,7 @@
],
"support": {
"issues": "https://github.com/doctrine/inflector/issues",
"source": "https://github.com/doctrine/inflector/tree/2.0.6"
"source": "https://github.com/doctrine/inflector/tree/2.0.8"
},
"funding": [
{
@ -225,7 +225,7 @@
"type": "tidelift"
}
],
"time": "2022-10-20T09:10:12+00:00"
"time": "2023-06-16T13:40:37+00:00"
},
{
"name": "doctrine/lexer",
@ -2081,16 +2081,16 @@
},
{
"name": "monolog/monolog",
"version": "3.3.1",
"version": "3.4.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "9b5daeaffce5b926cac47923798bba91059e60e2"
"reference": "e2392369686d420ca32df3803de28b5d6f76867d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/9b5daeaffce5b926cac47923798bba91059e60e2",
"reference": "9b5daeaffce5b926cac47923798bba91059e60e2",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/e2392369686d420ca32df3803de28b5d6f76867d",
"reference": "e2392369686d420ca32df3803de28b5d6f76867d",
"shasum": ""
},
"require": {
@ -2105,7 +2105,7 @@
"doctrine/couchdb": "~1.0@dev",
"elasticsearch/elasticsearch": "^7 || ^8",
"ext-json": "*",
"graylog2/gelf-php": "^1.4.2 || ^2@dev",
"graylog2/gelf-php": "^1.4.2 || ^2.0",
"guzzlehttp/guzzle": "^7.4.5",
"guzzlehttp/psr7": "^2.2",
"mongodb/mongodb": "^1.8",
@ -2113,7 +2113,7 @@
"phpstan/phpstan": "^1.9",
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-strict-rules": "^1.4",
"phpunit/phpunit": "^9.5.26",
"phpunit/phpunit": "^10.1",
"predis/predis": "^1.1 || ^2",
"ruflin/elastica": "^7",
"symfony/mailer": "^5.4 || ^6",
@ -2166,7 +2166,7 @@
],
"support": {
"issues": "https://github.com/Seldaek/monolog/issues",
"source": "https://github.com/Seldaek/monolog/tree/3.3.1"
"source": "https://github.com/Seldaek/monolog/tree/3.4.0"
},
"funding": [
{
@ -2178,7 +2178,7 @@
"type": "tidelift"
}
],
"time": "2023-02-06T13:46:10+00:00"
"time": "2023-06-21T08:46:11+00:00"
},
{
"name": "nesbot/carbon",
@ -2433,16 +2433,16 @@
},
{
"name": "nikic/php-parser",
"version": "v4.15.5",
"version": "v4.16.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e"
"reference": "19526a33fb561ef417e822e85f08a00db4059c17"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/11e2663a5bc9db5d714eedb4277ee300403b4a9e",
"reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/19526a33fb561ef417e822e85f08a00db4059c17",
"reference": "19526a33fb561ef417e822e85f08a00db4059c17",
"shasum": ""
},
"require": {
@ -2483,9 +2483,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.5"
"source": "https://github.com/nikic/PHP-Parser/tree/v4.16.0"
},
"time": "2023-05-19T20:20:00+00:00"
"time": "2023-06-25T14:52:30+00:00"
},
{
"name": "nunomaduro/termwind",
@ -3315,16 +3315,16 @@
},
{
"name": "react/async",
"version": "v4.0.0",
"version": "v4.1.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/async.git",
"reference": "2aa8d89057e1059f59666e4204100636249b7be0"
"reference": "b9641ac600b4b144e71a87dcf1be4d41dd3a3548"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/async/zipball/2aa8d89057e1059f59666e4204100636249b7be0",
"reference": "2aa8d89057e1059f59666e4204100636249b7be0",
"url": "https://api.github.com/repos/reactphp/async/zipball/b9641ac600b4b144e71a87dcf1be4d41dd3a3548",
"reference": "b9641ac600b4b144e71a87dcf1be4d41dd3a3548",
"shasum": ""
},
"require": {
@ -3333,7 +3333,8 @@
"react/promise": "^3.0 || ^2.8 || ^1.2.1"
},
"require-dev": {
"phpunit/phpunit": "^9.3"
"phpstan/phpstan": "1.10.18",
"phpunit/phpunit": "^9.5"
},
"type": "library",
"autoload": {
@ -3377,19 +3378,15 @@
],
"support": {
"issues": "https://github.com/reactphp/async/issues",
"source": "https://github.com/reactphp/async/tree/v4.0.0"
"source": "https://github.com/reactphp/async/tree/v4.1.0"
},
"funding": [
{
"url": "https://github.com/WyriHaximus",
"type": "github"
},
{
"url": "https://github.com/clue",
"type": "github"
"url": "https://opencollective.com/reactphp",
"type": "open_collective"
}
],
"time": "2022-07-11T14:21:02+00:00"
"time": "2023-06-22T14:10:50+00:00"
},
{
"name": "react/event-loop",
@ -4168,16 +4165,16 @@
},
{
"name": "symfony/http-foundation",
"version": "v6.3.0",
"version": "v6.3.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
"reference": "718a97ed430d34e5c568ea2c44eab708c6efbefb"
"reference": "e0ad0d153e1c20069250986cd9e9dd1ccebb0d66"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/718a97ed430d34e5c568ea2c44eab708c6efbefb",
"reference": "718a97ed430d34e5c568ea2c44eab708c6efbefb",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/e0ad0d153e1c20069250986cd9e9dd1ccebb0d66",
"reference": "e0ad0d153e1c20069250986cd9e9dd1ccebb0d66",
"shasum": ""
},
"require": {
@ -4225,7 +4222,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/http-foundation/tree/v6.3.0"
"source": "https://github.com/symfony/http-foundation/tree/v6.3.1"
},
"funding": [
{
@ -4241,20 +4238,20 @@
"type": "tidelift"
}
],
"time": "2023-05-19T12:46:45+00:00"
"time": "2023-06-24T11:51:27+00:00"
},
{
"name": "symfony/http-kernel",
"version": "v6.3.0",
"version": "v6.3.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
"reference": "241973f3dd900620b1ca052fe409144f11aea748"
"reference": "161e16fd2e35fb4881a43bc8b383dfd5be4ac374"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/241973f3dd900620b1ca052fe409144f11aea748",
"reference": "241973f3dd900620b1ca052fe409144f11aea748",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/161e16fd2e35fb4881a43bc8b383dfd5be4ac374",
"reference": "161e16fd2e35fb4881a43bc8b383dfd5be4ac374",
"shasum": ""
},
"require": {
@ -4338,7 +4335,7 @@
"description": "Provides a structured process for converting a Request into a Response",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/http-kernel/tree/v6.3.0"
"source": "https://github.com/symfony/http-kernel/tree/v6.3.1"
},
"funding": [
{
@ -4354,7 +4351,7 @@
"type": "tidelift"
}
],
"time": "2023-05-30T19:03:32+00:00"
"time": "2023-06-26T06:07:32+00:00"
},
{
"name": "symfony/mailer",
@ -5317,16 +5314,16 @@
},
{
"name": "symfony/routing",
"version": "v6.3.0",
"version": "v6.3.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
"reference": "827f59fdc67eecfc4dfff81f9c93bf4d98f0c89b"
"reference": "d37ad1779c38b8eb71996d17dc13030dcb7f9cf5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/routing/zipball/827f59fdc67eecfc4dfff81f9c93bf4d98f0c89b",
"reference": "827f59fdc67eecfc4dfff81f9c93bf4d98f0c89b",
"url": "https://api.github.com/repos/symfony/routing/zipball/d37ad1779c38b8eb71996d17dc13030dcb7f9cf5",
"reference": "d37ad1779c38b8eb71996d17dc13030dcb7f9cf5",
"shasum": ""
},
"require": {
@ -5379,7 +5376,7 @@
"url"
],
"support": {
"source": "https://github.com/symfony/routing/tree/v6.3.0"
"source": "https://github.com/symfony/routing/tree/v6.3.1"
},
"funding": [
{
@ -5395,7 +5392,7 @@
"type": "tidelift"
}
],
"time": "2023-04-28T15:57:00+00:00"
"time": "2023-06-05T15:30:22+00:00"
},
{
"name": "symfony/service-contracts",
@ -5813,16 +5810,16 @@
},
{
"name": "symfony/var-dumper",
"version": "v6.3.0",
"version": "v6.3.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
"reference": "6acdcd5c122074ee9f7b051e4fb177025c277a0e"
"reference": "c81268d6960ddb47af17391a27d222bd58cf0515"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/6acdcd5c122074ee9f7b051e4fb177025c277a0e",
"reference": "6acdcd5c122074ee9f7b051e4fb177025c277a0e",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/c81268d6960ddb47af17391a27d222bd58cf0515",
"reference": "c81268d6960ddb47af17391a27d222bd58cf0515",
"shasum": ""
},
"require": {
@ -5875,7 +5872,7 @@
"dump"
],
"support": {
"source": "https://github.com/symfony/var-dumper/tree/v6.3.0"
"source": "https://github.com/symfony/var-dumper/tree/v6.3.1"
},
"funding": [
{
@ -5891,7 +5888,7 @@
"type": "tidelift"
}
],
"time": "2023-05-25T13:09:35+00:00"
"time": "2023-06-21T12:08:28+00:00"
},
{
"name": "tightenco/ziggy",
@ -6627,16 +6624,16 @@
},
{
"name": "laravel/breeze",
"version": "v1.21.0",
"version": "v1.21.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/breeze.git",
"reference": "a7e7e2acfb2fd332183aae41c445be7a2329e93e"
"reference": "1cb124f74debc1d5914a7bf2856b793c44ba396d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/breeze/zipball/a7e7e2acfb2fd332183aae41c445be7a2329e93e",
"reference": "a7e7e2acfb2fd332183aae41c445be7a2329e93e",
"url": "https://api.github.com/repos/laravel/breeze/zipball/1cb124f74debc1d5914a7bf2856b793c44ba396d",
"reference": "1cb124f74debc1d5914a7bf2856b793c44ba396d",
"shasum": ""
},
"require": {
@ -6685,20 +6682,20 @@
"issues": "https://github.com/laravel/breeze/issues",
"source": "https://github.com/laravel/breeze"
},
"time": "2023-05-04T15:02:53+00:00"
"time": "2023-06-16T21:23:43+00:00"
},
{
"name": "laravel/pint",
"version": "v1.10.2",
"version": "v1.10.3",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
"reference": "f78de1a1bbab7aa41a6ea211c5962b0530d1a301"
"reference": "c472786bca01e4812a9bb7933b23edfc5b6877b7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/pint/zipball/f78de1a1bbab7aa41a6ea211c5962b0530d1a301",
"reference": "f78de1a1bbab7aa41a6ea211c5962b0530d1a301",
"url": "https://api.github.com/repos/laravel/pint/zipball/c472786bca01e4812a9bb7933b23edfc5b6877b7",
"reference": "c472786bca01e4812a9bb7933b23edfc5b6877b7",
"shasum": ""
},
"require": {
@ -6709,7 +6706,7 @@
"php": "^8.1.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.17.0",
"friendsofphp/php-cs-fixer": "^3.18.0",
"illuminate/view": "^10.5.1",
"laravel-zero/framework": "^10.0.2",
"mockery/mockery": "^1.5.1",
@ -6751,20 +6748,20 @@
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
"time": "2023-06-12T10:50:04+00:00"
"time": "2023-06-20T15:55:03+00:00"
},
{
"name": "laravel/sail",
"version": "v1.22.0",
"version": "v1.23.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/sail.git",
"reference": "923e1e112b6a8598664dbb0ee79dd3137f1c9d56"
"reference": "a2e046f748e87d3ef8b2b381e0e5c5a11f34e46b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/sail/zipball/923e1e112b6a8598664dbb0ee79dd3137f1c9d56",
"reference": "923e1e112b6a8598664dbb0ee79dd3137f1c9d56",
"url": "https://api.github.com/repos/laravel/sail/zipball/a2e046f748e87d3ef8b2b381e0e5c5a11f34e46b",
"reference": "a2e046f748e87d3ef8b2b381e0e5c5a11f34e46b",
"shasum": ""
},
"require": {
@ -6816,7 +6813,7 @@
"issues": "https://github.com/laravel/sail/issues",
"source": "https://github.com/laravel/sail"
},
"time": "2023-05-04T14:52:56+00:00"
"time": "2023-06-16T21:20:12+00:00"
},
{
"name": "mockery/mockery",

@ -14,7 +14,7 @@ return new class extends Migration
public function up()
{
Schema::create('permissions', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->ulid('id')->primary();
$table->string('label');
$table->string('name');
$table->timestamps();

@ -50,7 +50,6 @@ services:
mariadb:
image: mariadb:latest
container_name: voucher-mariadb
restart: always
ports:
- 3306:3306
volumes:
@ -67,6 +66,24 @@ services:
networks:
voucher:
ipv4_address: 10.25.10.99
postgresql:
image: postgres:14-alpine3.17
container_name: voucher-postgres
ports:
- 5432:5432
volumes:
- postgres:/var/lib/postgresql/data
- ./database:/database
environment:
POSTGRES_DB: voucher
POSTGRES_USER: aji
POSTGRES_PASSWORD: eta
mem_limit: 254m
mem_reservation: 128M
cpus: 0.5
networks:
voucher:
ipv4_address: 10.25.10.199
portainer:
image: portainer/portainer-ce:latest
ports:
@ -74,7 +91,20 @@ services:
volumes:
- data:/data
- /var/run/docker.sock:/var/run/docker.sock
restart: unless-stopped
networks:
- voucher
phpcli:
image: voucher
container_name: voucher-app-cli
volumes:
- ./:/var/www
working_dir: /var/www
entrypoint: ["php","artisan", "schedule:work"]
mem_limit: 128M
mem_reservation: 128M
cpus: 0.5
depends_on:
- app
networks:
- voucher
@ -83,6 +113,8 @@ volumes:
driver: local
mariadb:
driver: local
postgres:
driver: local
networks:
voucher:

98
package-lock.json generated

@ -756,16 +756,16 @@
}
},
"node_modules/@floating-ui/core": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.3.0.tgz",
"integrity": "sha512-vX1WVAdPjZg9DkDkC+zEx/tKtnST6/qcNpwcjeBgco3XRNHz5PUA+ivi/yr6G3o0kMR60uKBJcfOdfzOFI7PMQ=="
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.3.1.tgz",
"integrity": "sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g=="
},
"node_modules/@floating-ui/dom": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.3.0.tgz",
"integrity": "sha512-qIAwejE3r6NeA107u4ELDKkH8+VtgRKdXqtSPaKflL2S2V+doyN+Wt9s5oHKXPDo4E8TaVXaHT3+6BbagH31xw==",
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.4.2.tgz",
"integrity": "sha512-VKmvHVatWnewmGGy+7Mdy4cTJX71Pli6v/Wjb5RQBuq5wjUYx+Ef+kRThi8qggZqDgD8CogCpqhRoVp3+yQk+g==",
"dependencies": {
"@floating-ui/core": "^1.3.0"
"@floating-ui/core": "^1.3.1"
}
},
"node_modules/@floating-ui/react": {
@ -811,9 +811,9 @@
}
},
"node_modules/@inertiajs/core": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@inertiajs/core/-/core-1.0.8.tgz",
"integrity": "sha512-8nr1C76bE9Pfb5OwDGtjtSsiy2g6EG5J7w/w4dWHmoQdY9g01F+VnN3+QYtf/fsFkd8l7A6HoCBgUEZHkwdhXg==",
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@inertiajs/core/-/core-1.0.9.tgz",
"integrity": "sha512-Vd/4akXnY90qwtPJ1nVGtLXYhyGZdDjvQ4rv5qeqFYmqLEARdhDd7Lku1BwkboZX6GSTgDkipBSSGTKuRemOkQ==",
"dev": true,
"dependencies": {
"axios": "^1.2.0",
@ -823,12 +823,12 @@
}
},
"node_modules/@inertiajs/react": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@inertiajs/react/-/react-1.0.8.tgz",
"integrity": "sha512-rbuP8IiRow7lMmAFWP7KzoogXCRphbCEwAmMDMRw18BtpgXMG/CD97Cvszsu3JeVbV0M0dH4fnIfetMO2CRx+A==",
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@inertiajs/react/-/react-1.0.9.tgz",
"integrity": "sha512-0H9qwReRhc3QCEONX6B+EYUSDKmJG0pJMeswxB2iq+nmeYO2lgnus9DiknZTeN2Y/iLcSnYvNcgGGSqQus1Qfg==",
"dev": true,
"dependencies": {
"@inertiajs/core": "1.0.8",
"@inertiajs/core": "1.0.9",
"lodash.isequal": "^4.5.0"
},
"peerDependencies": {
@ -1112,9 +1112,9 @@
}
},
"node_modules/browserslist": {
"version": "4.21.8",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.8.tgz",
"integrity": "sha512-j+7xYe+v+q2Id9qbBeCI8WX5NmZSRe8es1+0xntD/+gaWXznP8tFEkv5IgSaHf5dS1YwVMbX/4W6m937mj+wQw==",
"version": "4.21.9",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz",
"integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==",
"dev": true,
"funding": [
{
@ -1131,8 +1131,8 @@
}
],
"dependencies": {
"caniuse-lite": "^1.0.30001502",
"electron-to-chromium": "^1.4.428",
"caniuse-lite": "^1.0.30001503",
"electron-to-chromium": "^1.4.431",
"node-releases": "^2.0.12",
"update-browserslist-db": "^1.0.11"
},
@ -1164,9 +1164,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001503",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001503.tgz",
"integrity": "sha512-Sf9NiF+wZxPfzv8Z3iS0rXM1Do+iOy2Lxvib38glFX+08TCYYYGR5fRJXk4d77C4AYwhUjgYgMsMudbh2TqCKw==",
"version": "1.0.30001508",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001508.tgz",
"integrity": "sha512-sdQZOJdmt3GJs1UMNpCCCyeuS2IEGLXnHyAo9yIO5JJDjbjoVRij4M1qep6P6gFpptD1PqIYgzM+gwJbOi92mw==",
"dev": true,
"funding": [
{
@ -1425,9 +1425,9 @@
"integrity": "sha512-GJRqdiy2h+EXy6a8E6R+ubmqUM08BK0FWNq41k24fup6045biQ8NXxoXimiwegMQvFFV3t1emADdGNL1TlS61A=="
},
"node_modules/electron-to-chromium": {
"version": "1.4.430",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.430.tgz",
"integrity": "sha512-FytjTbGwz///F+ToZ5XSeXbbSaXalsVRXsz2mHityI5gfxft7ieW3HqFLkU5V1aIrY42aflICqbmFoDxW10etg==",
"version": "1.4.442",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.442.tgz",
"integrity": "sha512-RkrZF//Ya+0aJq2NM3OdisNh5ZodZq1rdXOS96G8DdDgpDKqKE81yTbbQ3F/4CKm1JBPsGu1Lp/akkna2xO06Q==",
"dev": true
},
"node_modules/error-stack-parser": {
@ -2201,9 +2201,9 @@
}
},
"node_modules/pirates": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
"integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==",
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
"integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
"engines": {
"node": ">= 6"
}
@ -2402,9 +2402,9 @@
}
},
"node_modules/react-datepicker": {
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.13.0.tgz",
"integrity": "sha512-1S8yAqzcHE+LjCjMrTXJfUkTVijTPogxUYrmQmSpmRJ23fdC2w8cg04jzaEAyesTzyUa06JzayZJKk85QHbvcw==",
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.14.1.tgz",
"integrity": "sha512-uiPfjD+25KI5WOfCAXlzQgSLyksTagk3wwKn1KGBdF19YtybFDregRmcoNNGveQHAbT10SJZdCvk/8pbc7zxJg==",
"dependencies": {
"@popperjs/core": "^2.9.2",
"classnames": "^2.2.6",
@ -2451,9 +2451,9 @@
}
},
"node_modules/react-icons": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.9.0.tgz",
"integrity": "sha512-ijUnFr//ycebOqujtqtV9PFS7JjhWg0QU6ykURVHuL4cbofvRCf3f6GMn9+fBktEFQOIVZnuAYLZdiyadRQRFg==",
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.10.1.tgz",
"integrity": "sha512-/ngzDP/77tlCfqthiiGNZeYFACw85fUjZtLbedmJ5DTlNDIwETxhwBzdOJ21zj4iJdvc0J3y7yOsX3PpxAJzrw==",
"peerDependencies": {
"react": "*"
}
@ -2630,9 +2630,9 @@
}
},
"node_modules/rollup": {
"version": "3.25.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.1.tgz",
"integrity": "sha512-tywOR+rwIt5m2ZAWSe5AIJcTat8vGlnPFAv15ycCrw33t6iFsXZ6mzHVFh2psSjxQPmI+xgzMZZizUAukBI4aQ==",
"version": "3.25.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.3.tgz",
"integrity": "sha512-ZT279hx8gszBj9uy5FfhoG4bZx8c+0A1sbqtr7Q3KNWIizpTdDEPZbV2xcbvHsnFp4MavCQYZyzApJ+virB8Yw==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
@ -2787,9 +2787,9 @@
}
},
"node_modules/stylis": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.0.tgz",
"integrity": "sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ=="
},
"node_modules/sucrase": {
"version": "3.32.0",
@ -2836,9 +2836,9 @@
}
},
"node_modules/tabbable": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.1.2.tgz",
"integrity": "sha512-qCN98uP7i9z0fIS4amQ5zbGBOq+OSigYeGvPy7NDk8Y9yncqDZ9pRPgfsc2PJIVM9RrJj7GIfuRgmjoUU9zTHQ=="
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
"integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="
},
"node_modules/tailwindcss": {
"version": "3.3.2",
@ -2905,9 +2905,9 @@
}
},
"node_modules/tinymce": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/tinymce/-/tinymce-6.5.0.tgz",
"integrity": "sha512-I23hTUhbIyU/HCruy6wpIpOtr8UJhRJjRmJS9DCalN7UxT9iunkkbrcHa0FO9sG4GdMAOmVbZTHFBrgbDPYU6Q=="
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/tinymce/-/tinymce-6.5.1.tgz",
"integrity": "sha512-J67fxJiX3tjvVqer1dg1+cOxMeE2P55ESGhaakvqGPbAUU45HnCMLSioaOsxV1KfcXustw9WJo0rtn1SNQlVKQ=="
},
"node_modules/to-fast-properties": {
"version": "2.0.0",
@ -2945,9 +2945,9 @@
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="
},
"node_modules/tslib": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
"integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz",
"integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA=="
},
"node_modules/update-browserslist-db": {
"version": "1.0.11",

@ -2,50 +2,58 @@ import { formatIDR } from '@/utils'
import { router } from '@inertiajs/react'
import { HiMinusCircle, HiPlusCircle, HiTrash } from 'react-icons/hi2'
export default function VoucherCard({ item: { voucher, quantity } }) {
export default function VoucherCard({ item: { location_profile, quantity } }) {
const handleDelete = () => {
router.post(route('cart.store', { voucher: voucher, param: 'delete' }))
router.post(
route('cart.store', { profile: location_profile, param: 'delete' })
)
}
const handleAdd = () => {
router.post(route('cart.store', { voucher: voucher, param: 'add' }))
router.post(
route('cart.store', { profile: location_profile, param: 'add' })
)
}
const handleSub = () => {
router.post(route('cart.store', { voucher: voucher, param: 'sub' }))
router.post(
route('cart.store', { profile: location_profile, param: 'sub' })
)
}
return (
<div className="px-3 py-1 shadow-md rounded border border-gray-100">
<div className="text-base font-bold">
{voucher.location_profile.location.name}
{location_profile.location.name}
</div>
<div className="w-full border border-dashed"></div>
<div className="flex flex-row justify-between items-center">
<div>
<div className="text-xs text-gray-400 py-1">
{voucher.location_profile.display_note}
{location_profile.display_note}
</div>
<div className="text-xl font-bold">
Rp {formatIDR(voucher.validate_price)}
Rp {formatIDR(location_profile.validate_price)}
</div>
{+voucher.discount !== 0 && (
{+location_profile.discount !== 0 && (
<div className="flex flex-row space-x-2 items-center text-xs pb-2">
<div className="bg-red-300 text-red-600 px-1 py-0.5 font-bold rounded">
{voucher.discount}%
{location_profile.discount}%
</div>
<div className="text-gray-400 line-through">
{formatIDR(voucher.validate_display_price)}
{formatIDR(
location_profile.validate_display_price
)}
</div>
</div>
)}
</div>
<div className="flex flex-col justify-end text-right">
<div className="text-3xl font-bold">
{voucher.location_profile.quota}
{location_profile.quota}
</div>
<div className="text-gray-400">
{voucher.location_profile.diplay_expired}
{location_profile.display_expired}
</div>
</div>
</div>

@ -4,7 +4,7 @@ import { HiXMark } from 'react-icons/hi2'
import { useModalState } from '@/hooks'
import VoucherCard from '../Partials/VoucherCard'
import FormLocation from '../../Components/FormLocation'
import FormLocation from '@/Customer/Components/FormLocation'
import LocationModal from '../Partials/LocationModal'
const EmptyLocation = () => {
@ -33,7 +33,7 @@ export default function AllVoucher() {
const {
props: {
locations,
vouchers: { data, next_page_url },
profiles: { data, next_page_url },
_slocations,
},
} = usePage()
@ -68,10 +68,10 @@ export default function AllVoucher() {
{
replace: true,
preserveState: true,
only: ['vouchers'],
only: ['profiles'],
onSuccess: (res) => {
if (res.props.vouchers.data !== undefined) {
setItems(items.concat(res.props.vouchers.data))
if (res.props.profiles.data !== undefined) {
setItems(items.concat(res.props.profiles.data))
}
},
}
@ -88,8 +88,8 @@ export default function AllVoucher() {
replace: true,
preserveState: true,
onSuccess: (res) => {
if (res.props.vouchers.data !== undefined) {
setItems(res.props.vouchers.data)
if (res.props.profiles.data !== undefined) {
setItems(res.props.profiles.data)
return
}
setItems([])
@ -124,8 +124,8 @@ export default function AllVoucher() {
{/* voucher */}
<div className="flex flex-col w-full px-3 mt-3 space-y-2">
{items.map((voucher) => (
<VoucherCard key={voucher.id} voucher={voucher} />
{items.map((item) => (
<VoucherCard key={item.id} item={item} />
))}
{nextPageUrl !== null && (
<div

@ -29,7 +29,7 @@ const EmptyVoucher = () => {
export default function FavoriteVoucher() {
const {
props: {
vouchers: { data, next_page_url },
profiles: { data, next_page_url },
_flocations,
},
} = usePage()
@ -92,8 +92,8 @@ export default function FavoriteVoucher() {
{/* voucher */}
<div className="flex flex-col w-full px-3 mt-3 space-y-2">
{items.map((voucher) => (
<VoucherCard key={voucher.id} voucher={voucher} />
{items.map((item) => (
<VoucherCard key={item.id} item={item} />
))}
{nextPageUrl !== null && (
<div

@ -4,45 +4,39 @@ import { useModalState } from '@/hooks'
import { formatIDR } from '@/utils'
import { router } from '@inertiajs/react'
const Voucher = ({ voucher, onClick }) => {
const Voucher = ({ item, onClick }) => {
return (
<div
className="px-3 py-1 shadow-md rounded border border-gray-100 hover:bg-gray-50"
onClick={onClick}
>
<div className="w-full flex flex-row justify-between">
<div className="text-base font-bold">
{voucher.location_profile.location.name}
</div>
<div className="text-base font-bold">{item.location.name}</div>
<div className="text-sm text-gray-500"></div>
</div>
<div className="w-full border border-dashed"></div>
<div className="flex flex-row justify-between items-center">
<div>
<div className="text-xs text-gray-400 py-1">
{voucher.location_profile.display_note}
{item.display_note}
</div>
<div className="text-xl font-bold">
Rp {formatIDR(voucher.validate_price)}
Rp {formatIDR(item.validate_price)}
</div>
{+voucher.discount !== 0 && (
{+item.validate_discount !== 0 && (
<div className="flex flex-row space-x-2 items-center text-xs pb-2">
<div className="bg-red-300 text-red-600 px-1 py-0.5 font-bold rounded">
{voucher.discount}%
{item.validate_discount}%
</div>
<div className="text-gray-400 line-through">
{formatIDR(voucher.validate_display_price)}
{formatIDR(item.validate_display_price)}
</div>
</div>
)}
</div>
<div className="flex flex-col justify-end text-right">
<div className="text-3xl font-bold">
{voucher.location_profile.quota}
</div>
<div className="text-gray-400 ">
{voucher.location_profile.diplay_expired}
</div>
<div className="text-3xl font-bold">{item.quota}</div>
<div className="text-gray-400 ">{item.display_expired}</div>
</div>
</div>
</div>
@ -50,27 +44,27 @@ const Voucher = ({ voucher, onClick }) => {
}
const ModalChoose = (props) => {
const { state, voucher } = props
const { state, item } = props
const onDirectBuy = () => {
router.post(route('cart.store', voucher), { direct: 1 })
router.post(route('cart.store', item), { direct: 1 })
state.toggle()
}
const addToCarts = () => {
router.post(route('cart.store', voucher))
router.post(route('cart.store', item))
state.toggle()
}
return (
<BottomSheet isOpen={state.isOpen} toggle={() => state.toggle()}>
<Voucher voucher={voucher} />
{voucher.location_profile.display_note !== null && (
<Voucher item={item} />
{item.display_note !== null && (
<div
className="p-4 my-4 text-sm text-blue-800 rounded-lg bg-blue-50 dark:bg-gray-800 dark:text-blue-400"
role="alert"
>
{voucher.location_profile.display_note}
{item.display_note}
</div>
)}
<div className="flex flex-row justify-between gap-2 mt-2">
@ -91,7 +85,7 @@ const ModalChoose = (props) => {
)
}
export default function VoucherCard({ voucher }) {
export default function VoucherCard({ item }) {
const chooseModalState = useModalState()
const onVoucherChoose = () => {
@ -101,9 +95,9 @@ export default function VoucherCard({ voucher }) {
return (
<>
<div onClick={() => onVoucherChoose()}>
<Voucher voucher={voucher} />
<Voucher item={item} />
</div>
<ModalChoose state={chooseModalState} voucher={voucher} />
<ModalChoose state={chooseModalState} item={item} />
</>
)
}

@ -1,8 +1,14 @@
import React, { useState } from 'react'
import { Head, router } from '@inertiajs/react'
import { HiXMark, HiOutlineStar } from 'react-icons/hi2'
import CustomerLayout from '@/Layouts/CustomerLayout'
import VoucherCard from './VoucherCard'
import LocationModal from '../Index/Partials/LocationModal'
import FormLocation from '@/Customer/Components/FormLocation'
import { ALL, FAVORITE } from '../Index/utils'
import { useModalState } from '@/hooks'
const EmptyHere = () => {
return (
@ -19,86 +25,150 @@ export default function Exhange(props) {
const {
locations,
vouchers: { data, next_page_url },
_location_id,
_favorite,
_slocations,
_flocations,
} = props
const [locId, setLocId] = useState(_location_id)
const [v, setV] = useState(data)
const [favorite, setFavorite] = useState(_favorite)
const [vouchers, setVouchers] = useState(data)
const handleSelectLoc = (loc) => {
if (loc.id === locId) {
setLocId('')
fetch('')
return
}
setLocId(loc.id)
fetch(loc.id)
}
const [fLocations] = useState(_flocations)
const [sLocations, setSLocations] = useState(_slocations)
const locationModal = useModalState()
const handleNextPage = () => {
let location_ids = sLocations.map((l) => l.id)
router.get(
next_page_url,
{
location_id: locId,
},
{ location_ids },
{
replace: true,
preserveState: true,
only: ['vouchers'],
onSuccess: (res) => {
setV(v.concat(res.props.vouchers.data))
setVouchers(vouchers.concat(res.props.vouchers.data))
},
}
)
}
const fetch = (locId) => {
const fetch = (locations) => {
let location_ids = locations.map((l) => l.id)
router.get(
route(route().current()),
{ location_id: locId },
{ location_ids },
{
replace: true,
preserveState: true,
onSuccess: (res) => {
setV(res.props.vouchers.data)
setVouchers(res.props.vouchers.data)
},
}
)
}
const handleAddLocation = (location) => {
const isExists = sLocations.find((l) => l.id === location.id)
if (!isExists) {
const locations = [location].concat(...sLocations)
setSLocations(locations)
fetch(locations)
}
}
const handleRemoveLocation = (index) => {
const locations = sLocations.filter((_, i) => i !== index)
setSLocations(locations)
fetch(locations)
}
const isStatus = (s) => {
if (s === favorite) {
return 'px-2 py-1 rounded-2xl hover:bg-blue-800 text-white bg-blue-600 border border-blue-800'
}
return 'px-2 py-1 rounded-2xl hover:bg-blue-800 hover:text-white bg-blue-100 border border-blue-200'
}
const handleFavorite = () => {
setFavorite(FAVORITE)
fetch(fLocations)
}
const handleAll = () => {
setFavorite(ALL)
fetch(sLocations)
}
return (
<CustomerLayout>
<Head title="Poin" />
<div className="flex flex-col min-h-[calc(95dvh)]">
<div className="pt-5 text-2xl px-5 font-bold">Tukar poin</div>
<div className="pt-5 text-2xl px-5 font-bold">Tukar Poin</div>
<div className="px-5 text-gray-400 text-sm">
tukarkan poin anda dengan voucher manarik
</div>
{v.length <= 0 ? (
<EmptyHere />
) : (
<div className="w-full flex flex-col pt-5">
{/* chips */}
<div className="w-full flex flex-row overflow-y-scroll space-x-2 px-2">
{locations.map((location) => (
<div className="w-full flex flex-col pt-5">
<div className="w-full flex flex-col">
<div className="w-full flex flex-row space-x-2 px-4">
<div className={isStatus(ALL)} onClick={handleAll}>
Semua
</div>
<div
className={isStatus(FAVORITE)}
onClick={handleFavorite}
>
Favorit
</div>
</div>
</div>
<div
className="w-full space-x-2 px-4 my-2"
onClick={locationModal.toggle}
>
<FormLocation placeholder="Cari Lokasi" />
</div>
{favorite === ALL ? (
<div className="w-full flex flex-row overflow-y-scroll space-x-2 px-4">
{sLocations.map((location, index) => (
<div
onClick={() => handleSelectLoc(location)}
className="flex flex-row items-center gap-1 px-2 py-1 rounded-2xl bg-blue-100 border border-blue-200 hover:bg-blue-500"
key={location.id}
className={`px-2 py-1 rounded-2xl ${
location.id === locId
? 'text-white bg-blue-600 border border-blue-800'
: 'bg-blue-100 border border-blue-200'
}`}
onClick={() => handleRemoveLocation(index)}
>
{location.name}
<div>{location.name}</div>
<div className="pl-2">
<HiXMark className="h-5 w-5 text-red-700" />
</div>
</div>
))}
</div>
) : (
<div className="w-full flex flex-row overflow-y-scroll space-x-2 px-4">
{fLocations.map((location, index) => (
<div
className="flex flex-row items-center gap-1 px-2 py-1 rounded-2xl bg-blue-100 border border-blue-200 hover:bg-blue-500"
key={location.id}
onClick={() => handleRemoveLocation(index)}
>
<div>{location.name}</div>
<div className="pl-2">
<HiOutlineStar className="h-5 w-5 text-yellow-300 fill-yellow-300" />
</div>
</div>
))}
</div>
)}
</div>
{vouchers.length <= 0 ? (
<EmptyHere />
) : (
<div className="w-full flex flex-col">
{/* voucher */}
<div className="flex flex-col w-full px-3 mt-3 space-y-2">
{v.map((voucher) => (
{vouchers.map((voucher) => (
<VoucherCard
key={voucher.id}
voucher={voucher}
@ -116,6 +186,11 @@ export default function Exhange(props) {
</div>
)}
</div>
<LocationModal
state={locationModal}
locations={locations}
onItemSelected={handleAddLocation}
/>
</CustomerLayout>
)
}

@ -81,7 +81,7 @@ export default function VoucherCard({ voucher }) {
{voucher.location_profile.quota}
</div>
<div className="text-gray-400 ">
{voucher.location_profile.diplay_expired}
{voucher.location_profile.display_expired}
</div>
</div>
</div>

@ -62,7 +62,7 @@ export default function VoucherCard(props) {
{voucher.location_profile.quota}
</div>
<div className="text-gray-400 ">
{voucher.location_profile.diplay_expired}
{voucher.location_profile.display_expired}
</div>
</div>
</div>

@ -1,13 +1,13 @@
import React from 'react';
import ApplicationLogo from '@/Components/Defaults/ApplicationLogo';
import { Link } from '@inertiajs/react';
import React from 'react'
import ApplicationLogo from '@/Components/Defaults/ApplicationLogo'
import { Link } from '@inertiajs/react'
export default function Guest({ children }) {
return (
<div className="min-h-screen flex flex-col sm:justify-center items-center pt-6 sm:pt-0 bg-gray-100 dark:bg-gray-900">
<div>
<Link href="/">
<ApplicationLogo className="w-auto h-20 fill-current text-gray-500 text-5xl font-bold" />
<ApplicationLogo className="w-auto h-20 fill-current text-gray-500 text-5xl font-bold text-center" />
</Link>
</div>
@ -15,5 +15,5 @@ export default function Guest({ children }) {
{children}
</div>
</div>
);
)
}

@ -154,7 +154,7 @@ export default function Index(props) {
scope="row"
className="py-4 px-6"
>
{profile.diplay_expired}
{profile.display_expired}
</td>
<td className="py-4 px-6 flex justify-end">
<Dropdown

@ -8,6 +8,39 @@ import { Head, useForm } from '@inertiajs/react'
import FormFile from '@/Components/FormFile'
import { formatIDR } from '@/utils'
const SaleItem = ({ item, index }) => {
const { voucher } = JSON.parse(item.additional_info_json)
console.log(voucher)
return (
<>
<td
scope="row"
className="py-4 px-6 font-medium text-gray-900 whitespace-nowrap dark:text-white"
>
{index + 1}
</td>
<td scope="row" className="py-4 px-6">
{voucher.location_profile.location.name}
</td>
<td scope="row" className="py-4 px-6">
{voucher.location_profile.name}
</td>
<td scope="row" className="py-4 px-6">
{voucher.username}
</td>
<td scope="row" className="py-4 px-6">
{voucher.comment}
</td>
<td scope="row" className="py-4 px-6">
{voucher.location_profile.quota}
</td>
<td scope="row" className="py-4 px-6">
{voucher.location_profile.display_expired}
</td>
</>
)
}
export default function Detail(props) {
const { sale } = props
@ -50,13 +83,10 @@ export default function Detail(props) {
Lokasi
</th>
<th scope="col" className="py-3 px-6">
Username
</th>
<th scope="col" className="py-3 px-6">
Password
Profile
</th>
<th scope="col" className="py-3 px-6">
Profile
Kode
</th>
<th scope="col" className="py-3 px-6">
Comment
@ -64,38 +94,19 @@ export default function Detail(props) {
<th scope="col" className="py-3 px-6">
Kuota
</th>
<th scope="col" className="py-3 px-6">
Masa Aktif
</th>
</tr>
</thead>
<tbody>
{sale.items.map((item, index) => (
<tr
className="bg-white border-b dark:bg-gray-800 dark:border-gray-700"
key={item.voucher.id}
key={item.id}
>
<td
scope="row"
className="py-4 px-6 font-medium text-gray-900 whitespace-nowrap dark:text-white"
>
{index + 1}
</td>
<td scope="row" className="py-4 px-6">
{item.voucher.location.name}
</td>
<td scope="row" className="py-4 px-6">
{item.voucher.username}
</td>
<td scope="row" className="py-4 px-6">
{item.voucher.password}
</td>
<td scope="row" className="py-4 px-6">
{item.voucher.profile}
</td>
<td scope="row" className="py-4 px-6">
{item.voucher.comment}
</td>
<td scope="row" className="py-4 px-6">
{item.voucher.display_quota}
</td>
<SaleItem item={item} index={index} />
</tr>
))}
</tbody>

@ -317,7 +317,7 @@ export default function Index(props) {
className="py-4 px-6"
>
<div
className={`p-2 border font-bold ${voucher.status.color} rounded-full`}
className={`p-2 border font-bold ${voucher.status.color} rounded-full text-center`}
>
{voucher.status.text}
</div>

@ -73,7 +73,7 @@ Route::middleware(['http_secure_aware', 'guard_should_customer', 'inertia.custom
// cart
Route::get('cart', [CartController::class, 'index'])->name('cart.index');
Route::post('cart/process', [CartController::class, 'purchase'])->name('cart.purchase');
Route::post('cart/{voucher}', [CartController::class, 'store'])->name('cart.store');
Route::post('cart/{profile}', [CartController::class, 'store'])->name('cart.store');
// notification
Route::get('notifications', [HomeController::class, 'notification'])->name('notification.index');

Loading…
Cancel
Save