#vue #javascript #frontend

Day 14: Final Project - Task Master

Welcome to Day 14! You made it. You are now a Vue developer.

To graduate, we are going to build Task Master.

  • Features: Add task, Delete task, Toggle Done, Filter (All/Favs).
  • Tech Stack: Pinia (State), Router (Pages), Composables (Logic).

1. The Store (stores/TaskStore.ts)

We need a store to manage our tasks.

import { defineStore } from 'pinia'

export const useTaskStore = defineStore('taskStore', {
  state: () => ({
    tasks: [
      { id: 1, title: "Learn Vue 3", isFav: true },
      { id: 2, title: "Build an App", isFav: false }
    ],
    name: "My Tasks"
  }),
  getters: {
    favs() {
      return this.tasks.filter(t => t.isFav)
    },
    favCount() {
      return this.tasks.reduce((p, c) => c.isFav ? p + 1 : p, 0)
    },
    totalCount: (state) => {
      return state.tasks.length
    }
  },
  actions: {
    addTask(task) {
      this.tasks.push(task)
    },
    deleteTask(id) {
      this.tasks = this.tasks.filter(t => t.id !== id)
    },
    toggleFav(id) {
      const task = this.tasks.find(t => t.id === id)
      if (task) task.isFav = !task.isFav
    }
  }
})

2. The Components

TaskDetails.vue

<script setup>
import { useTaskStore } from '../stores/TaskStore'

const props = defineProps(['task'])
const taskStore = useTaskStore()
</script>

<template>
  <div class="task">
    <h3>{{ task.title }}</h3>
    <div class="icons">
      <i 
        @click="taskStore.deleteTask(task.id)" 
        class="material-icons"
      >delete</i>
      <i 
        @click="taskStore.toggleFav(task.id)" 
        class="material-icons" 
        :class="{active: task.isFav}"
      >favorite</i>
    </div>
  </div>
</template>

TaskForm.vue

<script setup>
import { ref } from 'vue'
import { useTaskStore } from '../stores/TaskStore'

const taskStore = useTaskStore()
const newTask = ref('')

const handleSubmit = () => {
  if (newTask.value.length > 0) {
    taskStore.addTask({
      title: newTask.value,
      isFav: false,
      id: Math.floor(Math.random() * 10000)
    })
    newTask.value = ''
  }
}
</script>

<template>
  <form @submit.prevent="handleSubmit">
    <input 
      type="text" 
      placeholder="I need to..." 
      v-model="newTask"
    >
    <button>Add</button>
  </form>
</template>

3. The View (App.vue or HomeView.vue)

<script setup>
import { useTaskStore } from './stores/TaskStore'
import TaskDetails from './components/TaskDetails.vue'
import TaskForm from './components/TaskForm.vue'
import { ref } from 'vue'
import { storeToRefs } from 'pinia'

const taskStore = useTaskStore()
const { tasks, loading, favs, totalCount, favCount } = storeToRefs(taskStore)

const filter = ref('all')
</script>

<template>
  <main>
    <header>
      <h1>{{ taskStore.name }}</h1>
    </header>

    <div class="new-task-form">
      <TaskForm />
    </div>

    <nav class="filter">
      <button @click="filter = 'all'">All tasks</button>
      <button @click="filter = 'favs'">Fav tasks</button>
    </nav>

    <div class="task-list" v-if="filter === 'all'">
      <p>You have {{ totalCount }} tasks left to do</p>
      <div v-for="task in tasks">
        <TaskDetails :task="task" />
      </div>
    </div>

    <div class="task-list" v-if="filter === 'favs'">
      <p>You have {{ favCount }} favs task left to do</p>
      <div v-for="task in favs">
        <TaskDetails :task="task" />
      </div>
    </div>
  </main>
</template>

Conclusion

You have just built a state-managed, component-based application.

Where to go from here?

  1. Nuxt.js: The Next.js equivalent for Vue. Server Side Rendering (SSR) and auto-imports.
  2. VueUse: A massive collection of ready-made composables.
  3. UI Libraries: PrimeVue, Vuetify, or Tailwind.

Thank you for following this series. Go build something amazing!