Skip to content

Commit

Permalink
Add Increase/Decrease for value metric
Browse files Browse the repository at this point in the history
  • Loading branch information
saleem-hadad committed Jul 16, 2022
1 parent e9ac5ca commit ac50e0c
Show file tree
Hide file tree
Showing 21 changed files with 100,943 additions and 29 deletions.
9 changes: 9 additions & 0 deletions app/Contracts/HasPreviousRange.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace App\Contracts;

interface HasPreviousRange
{
public function previousRangeStart();
public function previousRangeEnd();
}
14 changes: 13 additions & 1 deletion app/Domain/Ranges/CurrentMonth.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace App\Domain\Ranges;

class CurrentMonth extends Range
use App\Contracts\HasPreviousRange;

class CurrentMonth extends Range implements HasPreviousRange
{
public function start()
{
Expand All @@ -13,4 +15,14 @@ public function end()
{
return now()->endOfMonth()->format("Y-m-d");
}

public function previousRangeStart()
{
return now()->subMonth()->startOfMonth()->format("Y-m-d");
}

public function previousRangeEnd()
{
return now()->subMonth()->endOfMonth()->format("Y-m-d");
}
}
14 changes: 13 additions & 1 deletion app/Domain/Ranges/CurrentYear.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace App\Domain\Ranges;

class CurrentYear extends Range
use App\Contracts\HasPreviousRange;

class CurrentYear extends Range implements HasPreviousRange
{
public function start()
{
Expand All @@ -13,4 +15,14 @@ public function end()
{
return now()->endOfYear()->format("Y-m-d");
}

public function previousRangeStart()
{
return now()->subYear()->startOfYear()->format("Y-m-d");
}

public function previousRangeEnd()
{
return now()->subYear()->endOfYear()->format("Y-m-d");
}
}
4 changes: 3 additions & 1 deletion app/GraphQL/Queries/TotalCash.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public function __invoke($_, array $args)
$investment = Transaction::investment()->sum('amount');
$savings = Transaction::savings()->sum('amount');

return $income - ($expenses + $investment + $savings);
return [
'value' => $income - ($expenses + $investment + $savings)
];
}
}
17 changes: 16 additions & 1 deletion app/GraphQL/Queries/TotalExpenses.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\Models\Transaction;
use App\Domain\Metrics\ValueMetric;
use App\Contracts\HasPreviousRange;

class TotalExpenses extends ValueMetric
{
Expand All @@ -21,6 +22,20 @@ public function __invoke($_, array $args)
$query->whereBetween('created_at', [$rangeData->start(), $rangeData->end()]);
}

return $query->sum('amount');
$previous = 0;

if(is_a($rangeData, HasPreviousRange::class)) {
$previous = Transaction::query()
->expenses()
->whereBetween('created_at', [
$rangeData->previousRangeStart(),
$rangeData->previousRangeEnd()
])->sum('amount');
}

return [
'value' => $query->sum('amount'),
'previous' => $previous
];
}
}
17 changes: 16 additions & 1 deletion app/GraphQL/Queries/TotalIncome.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\GraphQL\Queries;

use App\Contracts\HasPreviousRange;
use App\Models\Transaction;
use App\Domain\Metrics\ValueMetric;

Expand All @@ -21,6 +22,20 @@ public function __invoke($_, array $args)
$query->whereBetween('created_at', [$rangeData->start(), $rangeData->end()]);
}

return $query->sum('amount');
$previous = 0;

if(is_a($rangeData, HasPreviousRange::class)) {
$previous = Transaction::query()
->income()
->whereBetween('created_at', [
$rangeData->previousRangeStart(),
$rangeData->previousRangeEnd()
])->sum('amount');
}

return [
'value' => $query->sum('amount'),
'previous' => $previous
];
}
}
4 changes: 3 additions & 1 deletion app/GraphQL/Queries/TotalInvestment.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public function ranges()
*/
public function __invoke($_, array $args)
{
return Transaction::investment()->sum('amount');
return [
'value' => Transaction::investment()->sum('amount')
];
}
}
4 changes: 3 additions & 1 deletion app/GraphQL/Queries/TotalSavings.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public function ranges()
*/
public function __invoke($_, array $args)
{
return Transaction::savings()->sum('amount');
return [
'value' => Transaction::savings()->sum('amount')
];
}
}
2,036 changes: 2,035 additions & 1 deletion public/css/app.css

Large diffs are not rendered by default.

98,642 changes: 98,640 additions & 2 deletions public/js/app.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions public/mix-manifest.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"/js/app.js": "/js/app.js?id=e96de6b2d1fad95be392",
"/css/app.css": "/css/app.css?id=98c4b8afbce17b6bb3ca"
"/js/app.js": "/js/app.js",
"/css/app.css": "/css/app.css"
}
53 changes: 48 additions & 5 deletions resources/js/Components/Domain/ValueMetric.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,59 @@
import React, { useEffect, useState } from 'react';
import { TrendingUpIcon, TrendingDownIcon } from '@heroicons/react/solid';

import { query } from '../../Api';
import Card from "../Global/Card";
import LoadingView from "../Global/LoadingView";
import { formatNumber, getAppCurrency } from '../../Utils';

export default function ValueMetric({name, graphql_query, ranges}) {
const [data, setData] = useState(null);
const [value, setValue] = useState(null);
const [previous, setPrevious] = useState(null);
const [selectedRange, setSelectedRange] = useState(ranges ? ranges[0].key : null);

useEffect(async () => {
setData(null);
setValue(null);

let { data } = await query(graphql_query, selectedRange);
setData(data[graphql_query])
let parsedData = JSON.parse(data[graphql_query]);

setValue(parsedData.value)
setPrevious(parsedData.previous)
}, [selectedRange])

if(data == null) {
if(value == null) {
return (
<Card className="relative">
<LoadingView />
</Card>
)
}

const growthPercentage = () => {
return Math.abs(increaseOrDecrease())
}

const increaseOrDecrease = () => {
if (previous == 0 || previous == null || value == 0)
return 0

return (((value - previous) / previous) * 100).toFixed(2)
}

const increaseOrDecreaseLabel = () => {
switch (Math.sign(increaseOrDecrease())) {
case 1:
return 'Increase'
case 0:
return 'Constant'
case -1:
return 'Decrease'
}
}

const increaseColor = () => name.toLowerCase().includes("expense") ? 'text-red-500' : 'text-green-500'
const decreaseColor = () => name.toLowerCase().includes("expense") ? 'text-green-500' : 'text-red-500'

return (
<Card>
<div className="px-6 py-4">
Expand All @@ -39,8 +69,21 @@ export default function ValueMetric({name, graphql_query, ranges}) {
</div>

<p className="flex items-center text-4xl mb-4">
{ getAppCurrency() } { formatNumber(data) }
{ getAppCurrency() } { formatNumber(value) }
</p>

{increaseOrDecrease() !== 0 && <div className="flex">
{increaseOrDecreaseLabel() == 'Increase' && <TrendingUpIcon className={["mr-2 h-5 w-5", increaseColor()].join(' ')} aria-hidden="true" />}
{increaseOrDecreaseLabel() == 'Decrease' && <TrendingDownIcon className={["mr-2 h-5 w-5", decreaseColor()].join(' ')} aria-hidden="true" />}

{growthPercentage() !== 0 && <p className="text-gray-500 font-bold">
{growthPercentage()}% {increaseOrDecreaseLabel()}
</p>}

{growthPercentage() === 0 && <p className="text-gray-500 font-bold">
No Change
</p>}
</div>}
</div>
</Card>
);
Expand Down
12 changes: 12 additions & 0 deletions resources/js/Components/Domain/__tests__/ValueMetric.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,16 @@ it('it fetches data if a range is selected', async () => {
})

await waitFor(() => expect(screen.getByText(/2k/i)).toBeVisible())
})

it('it displays decrease if previous value is higher than current', async () => {
render(<ValueMetric name="Some Metric" graphql_query="valueMetricSampleQueryWithPreviousHigher"/>);

await waitFor(() => expect(screen.getByText(/25% Decrease/i)).toBeVisible())
})

it('it displays increase if previous value is lower than current', async () => {
render(<ValueMetric name="Some Metric" graphql_query="valueMetricSampleQueryWithPreviousLower"/>);

await waitFor(() => expect(screen.getByText(/50% Increase/i)).toBeVisible())
})
34 changes: 31 additions & 3 deletions resources/js/mocks/handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,38 @@ export const handlers = [
)
}),
graphql.query('valueMetricSampleQuery', (req, res, ctx) => {
let data = {
'valueMetricSampleQuery': JSON.stringify({
'value': req.body.variables.range === 'current-month' ? 3000 : 2000
})
};

return res(
ctx.data(data),
)
}),
graphql.query('valueMetricSampleQueryWithPreviousHigher', (req, res, ctx) => {
let data = {
'valueMetricSampleQueryWithPreviousHigher': JSON.stringify({
'value': 3000,
'previous': 4000
})
};

return res(
ctx.data(data),
)
}),
graphql.query('valueMetricSampleQueryWithPreviousLower', (req, res, ctx) => {
let data = {
'valueMetricSampleQueryWithPreviousLower': JSON.stringify({
'value': 3000,
'previous': 2000
})
};

return res(
ctx.data({
'valueMetricSampleQuery': req.body.variables.range === 'current-month' ? 3000 : 2000
}),
ctx.data(data),
)
}),
];
28 changes: 26 additions & 2 deletions tests/Feature/TotalExpensesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Tests\Feature;

use Carbon\Carbon;
use Tests\TestCase;
use App\Models\Brand;
use App\Models\Category;
Expand All @@ -13,7 +14,7 @@ class TotalExpensesTest extends TestCase
use RefreshDatabase;

/** @test */
public function it_returns_correct_data()
public function it_returns_correct_value()
{
$expensesCategory = Category::factory()->create(['type' => Category::EXPENSES]);

Expand All @@ -27,7 +28,30 @@ public function it_returns_correct_data()
}
')->assertJson([
'data' => [
'totalExpenses' => '"10001.0"'
'totalExpenses' => '{"value":10001.0,"previous":0.0}'
],
]);
}

/** @test */
public function it_returns_correct_previous_value()
{
// mock app date
Carbon::setTestNow(Carbon::create(2021, 1, 18));

$expensesCategory = Category::factory()->create(['type' => Category::EXPENSES]);

$expensesBrand = Brand::factory()->create(['category_id' => $expensesCategory->id]);

Transaction::factory()->create(['brand_id' => $expensesBrand, 'amount' => 10001, 'created_at' => now()->subYear()]);

$this->graphQL(/** @lang GraphQL */ '
{
totalExpenses(range: "current-year")
}
')->assertJson([
'data' => [
'totalExpenses' => '{"value":0.0,"previous":10001.0}'
],
]);
}
Expand Down
Loading

0 comments on commit ac50e0c

Please sign in to comment.