Skip to content

Commit

Permalink
feat: add emergency fund widget
Browse files Browse the repository at this point in the history
  • Loading branch information
jesusantguerrero committed Mar 28, 2024
1 parent fd1141a commit 9a6144d
Show file tree
Hide file tree
Showing 18 changed files with 342 additions and 10 deletions.
47 changes: 47 additions & 0 deletions app/Domains/Budget/Http/Controllers/BudgetFundController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace App\Domains\Budget\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Domains\AppCore\Models\Category;
use App\Domains\Budget\Models\BudgetTarget;
use App\Domains\Budget\Services\BudgetFundService;
use App\Domains\Budget\Services\BudgetTargetService;

class BudgetFundController extends Controller
{

public function index(BudgetFundService $budgetFundService) {
if (request()->get('json')) {
return $budgetFundService->list(request()->user()->current_team_id);
}
return inertia("Finance/EmergencyFund/Index",
[]);
}

public function show() {
return inertia("Finance/EmergencyFund/Index",
[]);
}

public function store(BudgetFundService $budgetFundService)
{
$postData = request()->post();
$budgetFundService->add(request()->user(), $postData);
return redirect()->back();
}

public function update(Category $category, BudgetTarget $budgetTarget, BudgetTargetService $budgetTargetService)
{
$postData = request()->post();
$budgetTargetService->update($category, $budgetTarget, request()->user(), $postData);
return redirect()->back();
}

public function complete(Category $category, BudgetTarget $budgetTarget, BudgetTargetService $budgetTargetService)
{
$postData = request()->post();
$budgetTargetService->complete($budgetTarget, $category, $postData);
return redirect()->back();
}
}
34 changes: 34 additions & 0 deletions app/Domains/Budget/Models/BudgetFund.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace App\Domains\Budget\Models;

use Illuminate\Database\Eloquent\Model;
use Modules\Watchlist\Models\Watchlist;
use App\Domains\AppCore\Models\Category;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class BudgetFund extends Model
{
use HasFactory;

protected $fillable = [
'team_id',
'user_id',
'amount',
'name',
'category_id',
'watchlist_id',
];

public function category()
{
return $this->belongsTo(Category::class);
}

public function watchlist()
{
return $this->belongsTo(Watchlist::class);
}


}
59 changes: 59 additions & 0 deletions app/Domains/Budget/Services/BudgetFundService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace App\Domains\Budget\Services;

use Exception;
use App\Models\User;
use App\Domains\AppCore\Models\Category;
use App\Domains\Budget\Models\BudgetFund;
use App\Domains\Budget\Models\BudgetTarget;

class BudgetFundService
{
public function update(Category $category, BudgetTarget $budgetTarget, User $user, $postData) {
if ($category->id !== $budgetTarget->category_id){
throw new Exception(__("This target doent belongs to this category"));
}

$budgetTarget->update([
...$postData,
'team_id' => $user->current_team_id,
'user_id' => $user->id,
'name' => $category->name,
'category_id' => $budgetTarget->category_id,
]);
}

public function add(User $user, mixed $postData)
{
return BudgetFund::create([
...$postData,
'name' => "Emergency fund",
'team_id' => $user->current_team_id,
"user_id" => $user->id
]);
}

public function list($teamId) {
$items = BudgetFund::where([
"team_id" => $teamId
])->get();

return $items->map(fn( $item) => [
...$item->toArray(),
...$this->getData($item)
]);
}

public function getData(BudgetFund $budgetFund) {
$available = $budgetFund->category->budgets[1]?->available ?? 0;
$expenses = $budgetFund->watchlist->fullData();
$expenseTotal = $expenses["month"]?->total ?? 0;

return [
'balance' => $available,
'monthlyExpense' => $expenseTotal,
'total' => $available / $expenseTotal,
];
}
}
2 changes: 2 additions & 0 deletions app/Domains/Budget/routes.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

use Illuminate\Support\Facades\Route;
use App\Domains\Budget\Http\Controllers\BudgetFundController;
use App\Domains\Budget\Http\Controllers\BudgetMonthController;
use App\Domains\Budget\Http\Controllers\BudgetTargetController;
use App\Domains\Budget\Http\Controllers\BudgetCategoryController;
Expand All @@ -18,4 +19,5 @@
Route::post('/budgets-import', 'import')->name('budget.import');
Route::get('/budgets-export', 'export')->name('budget.export');
});
Route::resource('/budget-funds', BudgetFundController::class);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('budget_funds', function (Blueprint $table) {
$table->id();
$table->foreignId('team_id');
$table->foreignId('user_id');
$table->foreignId('watchlist_team_id');
$table->foreignId('watchlist_id');
$table->foreignId('category_team_id');
$table->foreignId('category_id');
$table->string('name');
$table->text('description')->nullable();
$table->json('meta_data')->nullable();
$table->integer('index')->default(0);
$table->timestamps();
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('budget_funds');
}
};
4 changes: 2 additions & 2 deletions resources/js/Components/organisms/LogerApiSimpleSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const props = withDefaults(
selected: boolean;
}) => VNodeChild;
trackId: string;
label: string;
label?: string;
}>(),
{
customLabel: "label",
Expand Down Expand Up @@ -80,7 +80,7 @@ const getOptionFromLabel = (option: string): SelectOption => {
};
const resultParser = (apiOptions: Record<string, string>[], query: string = "") => {
let includeCustom = true;
let includeCustom = props.allowCreate;
const queryTag = query.toLowerCase();
const originalMap = apiOptions.map((option) => {
const optionLabel = props.customLabel ? option[props.customLabel] : option.label;
Expand Down
4 changes: 4 additions & 0 deletions resources/js/Components/templates/SettingsSectionNav.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ const sections = [
label: 'Budget',
url: `/teams/${teamId}`
},
{
label: 'Emergency Fund Builder',
url: `/budget-funds/`
},
// {
// label: 'Integrations',
// url: '/integrations'
Expand Down
4 changes: 2 additions & 2 deletions resources/js/Components/widgets/RandomMealCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,15 @@ const getRandomMeal = () => {

<template>
<div class="flex items-center justify-between px-4 py-3 rounded-lg bg-base-lvl-3">
<div class="flex items-center h-10 text-lg text-center capitalize text-secondary">
<div class="flex items-center text-center capitalize text-secondary">
<span>
{{ state.label }}
</span>
</div>
<div class="flex justify-between">
<LogerButton
variant="inverse"
class="rounded-full"
class="rounded-full text-xs h-6"
@click="getRandomMeal"
:processing="state.isLoading"
>
Expand Down
5 changes: 2 additions & 3 deletions resources/js/Pages/Dashboard/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import BudgetTracker from "@/domains/budget/components/BudgetTracker.vue";
import OccurrenceCard from '@/Components/Modules/occurrence/OccurrenceCard.vue';
import DashboardSpendings from './Partials/DashboardSpendings.vue';
import BudgetFundWidget from './Partials/BudgetFundWidget.vue';
import { useAppContextStore } from '@/store';
import { IOccurrenceCheck } from '@/Components/Modules/occurrence/models';
Expand Down Expand Up @@ -142,9 +143,7 @@
<DashboardDrafts v-else />
</template>
</WidgetContainer>
<WidgetContainer
:message="$t('Emergency Fund Builder')"
/>
<BudgetFundWidget />
</section>
</main>
</AppLayout>
Expand Down
40 changes: 40 additions & 0 deletions resources/js/Pages/Dashboard/Partials/BudgetFundWidget.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import WidgetContainer from '@/Components/WidgetContainer.vue';
import axios from 'axios';
import { formatMoney } from '@/utils';
const budgetData = ref({})
const fetchChecks = () => {
return axios.get(`/budget-funds?json=true`).then(({ data }) => {
budgetData.value = data?.at(0);
});
}
onMounted(() => {
fetchChecks()
})
</script>

<template>
<WidgetContainer
:message="$t('Emergency Fund Builder')"
>
<template #content>
<section>
<p>
{{ formatMoney(budgetData.balance) }}
</p>
<p>
{{ formatMoney(budgetData.monthlyExpense) }}
</p>
<p>
{{ budgetData.total?.toFixed?.(2) }} Months
</p>
</section>
</template>
</WidgetContainer>
</template>

1 change: 0 additions & 1 deletion resources/js/Pages/Dashboard/Partials/DashboardDrafts.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import { removeTransaction, draftsDBToTransaction, useTransactionModal } from '@/domains/transactions';
import LogerButton from '@/Components/atoms/LogerButton.vue';
import { useTransactionStore } from '@/store/transactions';
import { router } from '@inertiajs/vue3';
import MdiSync from '~icons/mdi/sync'
Expand Down
23 changes: 23 additions & 0 deletions resources/js/Pages/Finance/EmergencyFund/Index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script setup>
import AppLayout from '@/Components/templates/AppLayout.vue'
import { usePage } from '@inertiajs/vue3'
import BudgetFundForm from './Partials/BudgetFundForm.vue'
import SettingsSectionNav from '@/Components/templates/SettingsSectionNav.vue'
defineProps(['sessions'])
const pageProps = usePage().props
</script>


<template>
<AppLayout title="Emergency Fund Builder">
<template #header>
<SettingsSectionNav />
</template>

<section class="max-w-7xl mx-auto space-y-5 [&>*]:pt-5 divide-y pt-16 pb-20 sm:px-6 lg:px-8">
<BudgetFundForm :user="pageProps.user" />
</section>
</AppLayout>
</template>

Loading

0 comments on commit 9a6144d

Please sign in to comment.