Keith

Vue JS Expense Tracker With Local Storage

by Keith Rowles • 10/11/2023Vue

Design element guages

Summary

A Vue JS APP - single page expense tracker application using local storage for data persistance.

The application keeps track of all your expenses and revenue.

Via the online form, you can add in a title and amount then click the add transaction button.

You will then see your total income and total expenses in the display area… as well as a history of all your transactions.

Tech

  • HTML
  • CSS
  • JavaScript
  • Vue JS
  • Vue Toastification
  • Vite
  • Components
  • Vercel

Sample Code

For full code view the repository on GitHub. The link is provided below.

App.vue

<template>
  <div>
    <HeaderNav />
    <div class="container">
      <Balance :total="+total" />
      <IncomeExpenses :income="+income" :expenses="+expenses" />
      <TransactionList
        :transactions="transactions"
        @transactiondeleted="handleTransactionDeleted"
      />
      <AddTransaction @transactionSubmitted="handleTransactionSubmitted" />
    </div>
  </div>
</template>

<script setup>
import HeaderNav from '@/components/HeaderNav.vue';
import Balance from '@/components/Balance.vue';
import IncomeExpenses from '@/components/IncomeExpenses.vue';
import TransactionList from '@/components/TransactionList.vue';
import AddTransaction from '@/components/AddTransaction.vue';
import { ref, computed, onMounted } from 'vue';
import { useToast } from 'vue-toastification';

const toast = useToast();

const transactions = ref([]);

onMounted(() => {
  const savedTransactions = JSON.parse(localStorage.getItem('transactions'));

  if (savedTransactions) {
    transactions.value = savedTransactions;
  }
});

// const transactions = ref([
//   { id: 1, text: 'Flower', amount: -20 },
//   { id: 2, text: 'Salary', amount: 300 },
//   { id: 3, text: 'Book', amount: -10 },
//   { id: 4, text: 'Camera', amount: 150 },
// ]);

// get total
const total = computed(() => {
  return transactions.value.reduce((acc, transaction) => {
    return acc + transaction.amount;
  }, 0);
});

// Get income
const income = computed(() => {
  return transactions.value
    .filter((transaction) => transaction.amount > 0)
    .reduce((acc, transaction) => acc + transaction.amount, 0)
    .toFixed(2);
});

// Get expenses
const expenses = computed(() => {
  return transactions.value
    .filter((transaction) => transaction.amount < 0)
    .reduce((acc, transaction) => acc + transaction.amount, 0)
    .toFixed(2);
});

// Add transaction
const handleTransactionSubmitted = (data) => {
  // console.log(data);
  transactions.value.push({
    id: generateUniqueId(),
    text: data.text,
    amount: data.amount,
  });

  saveTransactionsToLocalStorage();

  toast.success('Transaction added');
};

// unique id
const generateUniqueId = () => {
  return Math.floor(Math.random() * 100000);
};

// Delete transaction
const handleTransactionDeleted = (id) => {
  transactions.value = transactions.value.filter(
    (transaction) => transaction.id !== id
  );

  saveTransactionsToLocalStorage();

  toast.success('Transaction Deleted');
};

// Save to local storage
const saveTransactionsToLocalStorage = () => {
  localStorage.setItem('transactions', JSON.stringify(transactions.value));
};
</script>

<style lang="scss" scoped></style>

TransactionList.vue

<template>
  <h3>History</h3>
  <ul id="list" class="list">
    <li
      v-for="transaction in transactions"
      :key="transaction.id"
      :class="transaction.amount < 0 ? 'minus' : 'plus'"
    >
      {{ transaction.text }} <span>${{ transaction.amount }}</span
      ><button class="delete-btn" @click="deleteTransaction(transaction.id)">
        x
      </button>
    </li>
  </ul>
</template>

<script setup>
import { defineProps } from 'vue';

const emit = defineEmits(['transactiondeleted']);

const props = defineProps({
  transactions: {
    type: Array,
    required: true,
  },
});

const deleteTransaction = (id) => {
  emit('transactiondeleted', id);
};
</script>

<style lang="scss" scoped></style>

Demo

Open demo on Vercel.

Link to Demo

Repo

Open repository on GitHub.

Link to Demo