Merge branch 'rate-history-refactored' of gitlab.com:CIEFWorldwideSdnBhd/exchange-2.0 into development

This commit is contained in:
edmondlang
2023-02-16 23:20:55 +08:00
13 changed files with 611 additions and 1 deletions
@@ -0,0 +1,20 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class CreatedAtBetween implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->whereBetween('created_at', $value);
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class CurrencyRateIdIn implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->whereIn('currency_rate_id', $value);
}
}
@@ -0,0 +1,84 @@
<?php
namespace App\Classes\Modules\Currencies\ControllersLogic\History;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Currencies\Services\Rates\ListsRateLogs;
use App\Classes\Modules\Currencies\Services\Rates\ListsRates;
use App\Classes\Modules\Currencies\Standards\Rules\Rates\CanListRates;
use App\Http\Resources\CurrencyRateLogResource;
use App\Models\CurrencyRateLog;
use ErrorException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Carbon\Carbon;
class ListCurrencyRateHistoryLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification(): array
{
return [
'title' => 'Retrieved Currency Rate History',
'message' => 'You have successfully retrieved a list of Currency Rate History'
];
}
/** @var CanListRates */
private $canListRates;
/** @var ListsRateLogs */
private $listsRateLogs;
/** @var ListsRate */
private $listsRates;
/**
* ListRatesLogic constructor.
* @param CanListRates $canListRates
* @param ListsRates $listsRates
*/
public function __construct(CanListRates $canListRates, ListsRateLogs $listsRateLogs, listsRates $listsRates)
{
$this->canListRates = $canListRates;
$this->listsRateLogs = $listsRateLogs;
$this->listsRates = $listsRates;
}
/**
* @param Request $request
* @return JsonResponse
* @throws ErrorException
*/
public function logic(Request $request): JsonResponse
{
//$this->canListRates->passes();
$filterArray = json_decode($request->input('filters'));
$rate_filter['currency_id'] = $filterArray->currency_id;
$rate_filter['service_id'] = $filterArray->service_id;
$currency_rate_ids = $this->listsRates->execute(
[
'currency_id' => $filterArray->currency_id,
'service_id' => $filterArray->service_id,
]
)->pluck('id')->toArray();
$query = $this->listsRateLogs->execute(
[
'currency_rate_id_in' => $currency_rate_ids,
'date_start' => new Carbon($filterArray->date_from),
'date_end' => new Carbon($filterArray->date_to),
]
);
return $this->collectionResponse(CurrencyRateLogResource::collection($query));
}
}
@@ -0,0 +1,33 @@
<?php
namespace App\Classes\Modules\Currencies\Services\Rates;
use App\Classes\General\Eloquent\AbstractListRecord;
use Illuminate\Database\Eloquent\Builder;
use App\Models\CurrencyRateLog;
class ListsRateLogs extends AbstractListRecord
{
/** @var CurrencyRateLog */
private $repository;
/**
* ListsRates constructor.
* @param CurrencyRate $repository
*/
public function __construct(CurrencyRateLog $repository)
{
$this->repository = $repository;
}
/**
* @return Builder
*/
function getRepository(): Builder
{
return $this->repository->newQuery();
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Currencies\History;
use App\Classes\Modules\Currencies\ControllersLogic\History\ListCurrencyRateHistoryLogic;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ListCurrencyRateHistory
{
/**
* @param Request $request
* @param ListRatesLogic $logic
* @return JsonResponse
*/
public function list(Request $request, ListCurrencyRateHistoryLogic $logic): JsonResponse {
return $logic->execute($request);
}
}
@@ -0,0 +1,26 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class CurrencyRateLogResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
$currencyRate = $this->currencyRate;
return [
'currency_rate_id' => $this->currency_rate_id,
'rate' => $this->selling,
'created_at' => $this->created_at->format('d-m-Y'),
'payment_method_type' => $currencyRate->payment_method_type,
];
}
}
+9
View File
@@ -1,10 +1,19 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class CurrencyRateLog extends AbstractModel
{
protected $table = 'currency_rate_logs';
/**
* @return BelongsTo
*/
public function currencyRate(): BelongsTo
{
return $this->BelongsTo(CurrencyRate::class, 'currency_rate_id', 'id');
}
}
@@ -0,0 +1,118 @@
<template>
<div class="row">
<div class="col">
<div class="row" style="margin-left:1px; margin-right:1px;" v-if='selectedCurrency'>
<div class="col bg-primary text-white">
<div class="row h-100 align-items-center">
<div class="col-auto p-r-0">
<span class="p-t-20 p-b-20 flag-icon fs-14"
:class="'flag-icon-' + selectedCurrency.country.short_code.toLowerCase()"></span>
</div>
<div class="col p-l-5 p-r-20">
{{ selectedCurrency.short_code }}
</div>
<div class="col-auto h-100 pointer" @click="recipientMenu = !recipientMenu">
<div class="row align-items-center bg-primary-light h-100">
<div class="col ">
<i class="fa fa-angle-down fs-14 m-t-5"
:class="[{ 'fa-angle-down': !recipientMenu }, { 'fa-angle-up': recipientMenu }]"></i>
</div>
</div>
</div>
<div class="relative w-100 text-master">
<div class="absolute w-100 b-l b-b b-r b-success" :class="[{ 'hide': !recipientMenu }]"
style="top: 100%; right: 0; z-index: 1;">
<div class="row text-left no-margin bg-white">
<div class="col no-padding">
<div class="row no-margin" v-for="currency in currencyList"
v-bind:key="currency.id">
<div class="col b-b b-grey p-t-10 p-b-10 pointer hover-t-10 p-b-10 "
:class="[{ 'bg-primary-light': selectedCurrency.id === currency.id }, { 'text-white': selectedCurrency.id === currency.id }, { 'hover-primary': selectedCurrency.id !== currency.id }, { 'pointer': selectedCurrency.id !== currency.id }]"
@click="UpdateCurrency(currency)">
<div class="row align-items-center justify-content-center">
<div class="col">
<div class="row align-items-center justify-content-center">
<div class="col-auto p-r-5 p-l-0">
<span class="flag-icon fs-14"
:class="'flag-icon-' + currency.country.short_code.toLowerCase()"></span>
</div>
<div class="col-auto p-l-5">
<div class="font-heading fs-12">
{{ currency.short_code }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
section: 'chooseCurrencySection',
currencyList: [
// {
// "id": 1,
// "country": {
// "id": 1,
// "name": "Malaysia",
// "short_code": "MY",
// "phone_code": "60"
// },
// "name": "Malaysian Ringgit",
// "short_code": "MYR",
// "symbol": "RM"
// },
{
"id": 2,
"country": {
"id": 2,
"name": "China",
"short_code": "CN",
"phone_code": "86"
},
"name": "Yuan Renminbi",
"short_code": "RMB",
"symbol": "¥"
},
{
"id": 3,
"country": {
"id": 3,
"name": "United States",
"short_code": "US",
"phone_code": "1"
},
"name": "US Dollar",
"short_code": "USD",
"symbol": "$"
}
],
recipientMenu: false,
selectedCurrency: null,
};
},
mounted() {
this.UpdateCurrency(Object.values(this.currencyList)[0]);
},
methods: {
UpdateCurrency(currency) {
this.selectedCurrency = currency;
this.recipientMenu = false;
this.$emit('input', currency.id);
},
}
}
</script>
@@ -0,0 +1,97 @@
<template>
<div class="row h-100 mt-1 mb-1 m-md-0">
<div class="col">
<!-- <loading-component style="height: 100%; top: 0;" key="1" color="success" v-show="isLoading"></loading-component> -->
<div class="row h-100" style="margin-left:1px; margin-right:1px; min-height: 50px;" v-if='!isLoading'>
<div class="col bg-primary text-white">
<div class="row h-100 align-items-center">
<div class="col p-l-5 p-r-20">
<span class="m-l-10">{{ selectedCurrency.name }}</span>
</div>
<div class="col-auto h-100 pointer" @click="recipientMenu = !recipientMenu">
<div class="row align-items-center bg-primary-light h-100">
<div class="col ">
<i class="fa fa-angle-down fs-14 m-t-5"
:class="[{ 'fa-angle-down': !recipientMenu }, { 'fa-angle-up': recipientMenu }]"></i>
</div>
</div>
</div>
<div class="relative w-100 text-master">
<div class="absolute w-100 b-l b-b b-r b-success" :class="[{ 'hide': !recipientMenu }]"
style="top: 100%; right: 0; z-index: 1;">
<div class="row text-left no-margin bg-white">
<div class="col no-padding">
<div class="row no-margin" v-for="currency in currencyList"
v-bind:key="currency.id">
<div class="col b-b b-grey p-t-10 p-b-10 pointer hover-t-10 p-b-10 "
:class="[{ 'bg-primary-light': selectedCurrency.id === currency.id }, { 'text-white': selectedCurrency.id === currency.id }, { 'hover-primary': selectedCurrency.id !== currency.id }, { 'pointer': selectedCurrency.id !== currency.id }]"
@click="UpdateCurrency(currency)">
<div class="row align-items-center justify-content-center">
<div class="col">
<div class="row align-items-center justify-content-center">
<div class="col-auto p-l-5">
<div class="font-heading fs-12">
{{ currency.name }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
section: 'chooseCurrencySection',
currencyList: null,
recipientMenu: false,
selectedCurrency: null,
isLoading: true,
};
},
computed: {
pendingQueue() {
return this.$store.getters.isInCompleteQueue(this.section);
}
},
watch: {
pendingQueue(inComplete) {
if (inComplete) {
this.fetchCurrency();
}
}
},
created() {
this.$store.dispatch('updateListQueue', { 'name': this.section });
},
methods: {
fetchCurrency() {
this.submit(route('api.service_type.list'), 'get', this.section, false, false);
},
successHandler(response) {
this.isLoading = false;
this.currencyList = response.payload.data;
this.UpdateCurrency(this.currencyList[0]);
this.$emit('loaded', true);
},
UpdateCurrency(currency) {
this.selectedCurrency = currency;
this.recipientMenu = false;
this.$emit('input', currency.id);
},
}
}
</script>
@@ -0,0 +1,168 @@
<template>
<div class="row">
<div class="col">
<div class="row">
<div class="col-12 col-md">
<choose-currency-component v-on:input="updateCurrencyId($event)"></choose-currency-component>
</div>
<div class="col-12 col-md">
<choose-service-component v-on:input="updateServiceId($event)" v-on:loaded="isLoading=!isLoading"></choose-service-component>
</div>
<div class="col-12 col-md">
<validation-wrapper-component :validator="$v.filters.date_from">
<label class="all-caps">Date From</label>
<date-picker-component v-model.lazy="filters.date_from"></date-picker-component>
</validation-wrapper-component>
</div>
<div class="col-12 col-md">
<validation-wrapper-component :validator="$v.filters.date_to">
<label class="all-caps">Date To</label>
<date-picker-component v-model.lazy="filters.date_to"></date-picker-component>
</validation-wrapper-component>
</div>
</div>
<div class="row m-t-15">
<div class="col">
<button type="button" class="btn btn-lg btn-primary fs-11 " @click="fetchRateLogs" :class="[{'disabled': isLoading}]" :disabled="isLoading">Search</button>
<!-- <button type="button" class="btn btn-lg btn-secondary fs-11" @click="resetSarch">Reset</button> -->
</div>
</div>
<loading-component style="height: 200px; top: 0;" key="1" color="success" v-show="isLoading"></loading-component>
<div class="row" v-if="!isLoading && !hasError">
<div class="col-12 mx-auto p-0 " style="width: 800px; height:350px">
<canvas id="currency-history-chart"></canvas>
</div>
</div>
<div class="row" v-if="hasError">
<div class="col">
<div class="row align-items-center justify-content-center hint-text">
<div class="col-4 hint-text"><img src="/images/not-found-illustration.png" class="w-100 hint-text"/></div>
</div>
<div class="row text-center">
<div class="col">
<div class="row m-t-20">
<div class="col">
<p class="all-caps no-margin fs-11" style="letter-spacing: 2px;">Nothing To Show Here</p>
</div>
</div>
<div class="row m-t-5 align-items-center justify-content-center">
<div class="col">
<small class="fs-9 muted all-caps font-lato" style="letter-spacing: 2px">There is no results found, Try adjusting your filters to find what you are looking for.</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Chart from 'chart.js';
export default {
data() {
return {
rateHistory: null,
isLoading: true,
hasError: false,
section: 'rateHistorySection',
minWidth: {
minWidth: "100px",
},
minWidth150: {
minWidth: "150px",
},
filters: {
currency_id: null,
service_id: null,
date_to: null,
date_from: null,
},
chartLebels:[],
chartData: []
};
},
computed: {
pendingQueue() {
return this.$store.getters.isInCompleteQueue(this.section);
}
},
watch: {
pendingQueue(inComplete) {
if (inComplete) {
this.resetSarch();
}
}
},
created() {
this.$store.dispatch('updateListQueue', { 'name': this.section });
},
validations: {
filters: {
date_from: {},
date_to: {},
},
},
methods: {
fetchRateLogs() {
this.isLoading = true;
this.submit(route('api.currency.history.list') + '?filters=' + JSON.stringify(this.filters), 'get', this.section, false, false)
},
resetSarch() {
var currentDate = new Date();
this.filters.date_to =this.formatDate(new Date(currentDate.setDate(currentDate.getDate() + 3)));
this.filters.date_from =this.formatDate(new Date(currentDate.setDate(currentDate.getDate() - 6)));
},
updateCurrencyId(id){
this.filters.currency_id = id;
},
updateServiceId(id){
this.filters.service_id = id;
},
successHandler(response) {
this.isLoading = false;
this.rateHistory = response.payload.data;
},
formatDate($date) {
let d = new Date($date);
let ye = new Intl.DateTimeFormat('en', { year: 'numeric' }).format(d);
let mo = new Intl.DateTimeFormat('en', { month: '2-digit' }).format(d);
let da = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(d);
return(`${da}-${mo}-${ye}`);
},
errorHandler(error){
this.isLoading = false;
this.hasError = true;
// this.error = error.message;
}
},
watch: {
rateHistory: function(newVal) {
this.chartLebels = [];
this.chartData = [];
for (var i = 0; i < newVal.length; i++) {
this.chartLebels.push(newVal[i].created_at.split("-")[0]);
this.chartData.push(newVal[i]["rate"]);
}
setTimeout(() => {
const ctx = document.getElementById("currency-history-chart");
new Chart(ctx, {
type: 'line',
data: {
labels: this.chartLebels,
datasets: [{
label: 'Currency Rate History',
data: this.chartData,
fill: false,
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
}]
},
});
}, 300);
}
},
}
</script>
@@ -0,0 +1,8 @@
@extends('layouts.base_portal')
@section('inner_content')
<div class="row">
<div class="col p-t-15 p-b-15">
<rate-histories-component></rate-histories-component>
</div>
</div>
@endsection
+5 -1
View File
@@ -7,4 +7,8 @@ Route::group(['namespace' => 'Currencies', 'as' => 'currency.', 'prefix' => 'cur
Route::get('/list', 'ListCurrencyController@list')->name('list');
Route::post('/create', 'CreateCurrencyController@create')->name('create');
Route::delete('/delete/{id}', 'DeleteCurrencyController@delete')->name('delete');
});
Route::group(['prefix' => 'history', 'namespace' => 'History', 'as' => 'history.'], function () {
Route::get('/list', 'ListCurrencyRateHistory@list')->name('list');
});
});
+3
View File
@@ -542,5 +542,8 @@ Route::get('/refund/fix', function(){
});
Route::get('/currency-rate-history', function () {
return view('pages.rate_histories');
})->name('currency_rate.history');