Ejercicio de Manipulación del DOM

19/53

Lectura

¿Qué tipo de material es este?

Hola, espero que hasta aquí estés disfrutando el curso, recuerda que para dominar un lenguaje o framework de programación debes practicar. Justamente por esto he creado este material para ti, puedes hacerlo en tu entorno local o puedes hacerlo en alguna herramienta como codepen.io. La idea es que practiques lo que has aprendido hasta este punto del curso, te invito a que revises el código de tus compañeros y que te animes a dar feedback así todos podrán ir creciendo.

...

Regístrate o inicia sesión para leer el resto del contenido.

Aportes 42

Preguntas 0

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Hola a todos, realicé el ejercicio con unas cuantas funciones extras de crud.
–>
Screenshot:

TaskManager.vue

/* eslint-disable vue/html-self-closing */
<template lang="pug">
  #app
    .container
        .section.p-0
            div.mb-6
                <div :class="messageClass" v-if="message">
                    <button class="delete"  aria-label="delete"  @click="message=false"></button>
                    | {{messageText}}
                </div>
            p.title.is-large.is-info.mt-6.mb-0
                | Task Manager
            p.has-text-right
                button.button.is-small.is-primary(@click="modalClass='modal is-active'; clear()")
                   | <i class="fa fa-plus" aria-hidden="true"></i> <strong> New task</strong>
                button.button.is-small.is-danger.ml-4(@click="deleteSelected")
                   | <i class="fa fa-trash" aria-hidden="true"></i> <strong class="ml-2"> Delete selected tasks</strong>
        table.table.is-bordered.is-hoverable.mt-2.is-fullwidth(v-show="tasks.length > 0")
          thead
            tr
              th
                input(type="checkbox" v-model="selectedAll")
              th Name
              th Description
              th Time
              th Ops.
          tbody
            tr(v-for="(task, index) in tasks")
              td
                input(type="checkbox" v-model="selected" :value="index")
              td {{task.name}}
              td {{task.description}}
              td {{task.time}} hrs
              td
                a.button.is-danger.is-small.mr-2(@click="deleteTask(index)") <i class="fa fa-times" aria-hidden="true"></i>
                a.button.is-info.is-small(@click="editTask(task, index)") <i class="fa fa-edit" aria-hidden="true"></i>
          tfoot
            tr
              td.has-text-right(colspan="3")
                strong Total Time
              td.has-text-left(colspan="2")
                | {{getTotalTime}} hrs
        div(v-if="!tasks.length")
            p.message.is-info.p-4.mt-4
                strong No hay tareas aún
    div
        <div :class="modalClass">
            <div class="modal-background"></div>
                <div class="modal-content">
                    div.my-4
                        <div :class="messageClass" v-if="messageInDialog">
                            <button class="delete"  aria-label="delete"  @click="messageInDialog=false"></button>
                            | {{messageText}}
                        </div>

                    .card.p-5
                        .card-header.title.has-text-right
                            span(v-if="editTaskIndex == -1") Create a new task
                            span(v-else) Edit task
                        .card-content
                            .content
                                .field
                                    input.input(type="text" v-model="aTask.name" placeholder="Task name")
                                .field
                                    textarea.textarea(v-model="aTask.description" placeholder="Task Description")
                                .field.has-text-left
                                    input.input(type="number" v-model="aTask.time" placeholder="Time" style="width:120px")
                                .field(v-if="editTaskIndex == -1")
                                    a.button.is-primary.is-medium(@click="addTask") Save task
                                    a.button.is-secondary.is-medium.ml-3(@click="modalClass='modal'") Close
                                .field(v-else)
                                    a.button.is-primary.is-medium(@click="updateTask") Update task
                </div>
            <button class="modal-close is-large" @click="modalClass='modal'" aria-label="close"></button>
        </div>

</template>
<script>

export default {

  name: "Tasks",
  data () {
    return {
      selected: [],
      editTaskIndex: -1,
      messageText: "The task was successfully saved.",
      messageClass: "notification is-success",
      message: false,
      messageInDialog: false,
      aTask: {
        name: "",
        time: 0,
        description: ""
      },
      totalTime: 0,
      modalClass: "modal",
      tasks: []
    };
  },
  computed: {
    getTotalTime (key = "time") {
      return this.tasks.reduce((a, b) => +a + +b.time, 0);
    },
    selectedAll: {
      set (val) {
        this.selected = [];
        if (val) {
          for (let i = 0; i < this.tasks.length; i++) {
            this.selected.push(i);
          }
        }
      },
      get () {
        return this.selected.length === this.tasks.length;
      }
    }
  },
  watch: {
  },
  created () {
    this.tasks = JSON.parse(localStorage.getItem("tasks")) || [];
  },
  mounted () {
    console.log("App Mounted");
  },
  methods: {
    deleteSelected () {
      const sel = this.selected.length;
      if (sel <= 0) {
        this.showMessage("notification is-warning", true, "You haven't selected any task to be deleted");
        return;
      }
      if (sel > 0) {
        for (let i = 0; i <= (sel); i++) {
          // Delete selected items
          this.tasks.splice(i, sel);
        }
        localStorage.setItem("tasks", JSON.stringify(this.tasks));
        this.showMessage("notification is-info", true, sel + " task(s) deleted successfully");
        this.selected = [];
      }
    },
    updateTask () {
      if (this.aTask.name.length > 0 && !isNaN(this.aTask.time)) {
        this.tasks[this.editTaskIndex] = { name: this.aTask.name, time: this.aTask.time, description: this.aTask.description };
        localStorage.setItem("tasks", JSON.stringify(this.tasks));
        this.showMessage("notification is-success", true, "The task '" + this.tasks[this.editTaskIndex].name + "' was edited");
        this.editTaskIndex = -1;
        this.modalClass = "modal";
        setTimeout(() => {
          this.message = false;
        }, 3000);
      } else {
        this.showMessage("notification is-danger", true, "Please review you information", true);
      }
    },
    editTask (task, index) {
      this.modalClass = "modal is-active";
      this.aTask = task;
      this.editTaskIndex = index;
    },
    showMessage (messageClass = "notification is-info", show = true, messageText, inDialog = false) {
      this.messageText = messageText;
      this.messageClass = messageClass;
      if (inDialog) {
        this.messageInDialog = true;
      } else {
        this.message = show;
      }
      setTimeout(() => {
        !inDialog ? this.message = false : this.messageInDialog = false;
      }, 3000);
    },
    deleteTask (index) {
      const taskName = this.tasks[index].name;
      this.tasks.splice(index, 1);
      localStorage.setItem("tasks", JSON.stringify(this.tasks));
      this.showMessage("notification is-info", true, `The task ${taskName} was deleted`);
    },
    clear () {
      this.aTask.name = "";
      this.aTask.time = 0;
      this.aTask.description = "";
      this.editTaskIndex = -1;
    },
    addTask () {
      if (this.aTask.name.length > 0 && !isNaN(this.aTask.time)) {
        if (this.aTask.time > 0) {
          const newTask = { name: this.aTask.name, time: this.aTask.time, description: this.aTask.description };
          this.tasks.push(newTask);
          localStorage.setItem("tasks", JSON.stringify(this.tasks));
          this.modalClass = "modal";
          this.showMessage("notification is-success", true, "The task was successfully saved");
          this.clear();
        } else {
          this.showMessage("notification is-danger", true, "Data for time must be a positive number", true);
        }
      } else {
        this.showMessage("notification is-danger", true, "Please review you information", true);
        // this.modalClass = "modal";
        setTimeout(() => {
          this.message = false;
        }, 3000);
      }
    }
  }
};
</script>

main.scss

@import '../../node_modules/bulma/bulma.sass';

Reutilizando el ejercicio del Curso básico de Vue y agregando las funcionalidades de este ejercicio:

CodePen Link

Dejo mi código:

<template>
  <div id="app">
    <div class="tareas">
      <input type="text" name="" id="taskTitle" placeholder="Tarea">
      <input type="number" name="" id="taskTime" placeholder="Horas" min="1" step="1">
      <button @click="addTask">Agregar tarea</button>
    </div>
    <section v-if="tasks.length > 0">
      <div class="listaTareas">
        <ul>
          <li
            v-for="task, index in tasks"
            :key="index"
          >
            {{ task.title }} -> {{ task.time }} horas
          <button
            :key="index"
            @click="deleteTask"
          >
            Borrar tarea {{ task.title }}
          </button>
          </li>
        </ul>
      </div>

      <div class="tiempo">
        <p>Tiempo total: {{ totalTime }} horas</p>
      </div>
    </section>
    <section v-else>
      <h1>No hay tareas por el momento</h1>
    </section>
  </div>
</template>

<script>
export default {
  data() {
    return {
      tasks: [],
      newTask: {
        title: '',
        time: 0
      }
    };
  },

  methods: {
    addTask() {
      const title = document.getElementById('taskTitle').value;
      const time = parseInt(document.getElementById('taskTime').value);
      if (title !== '' && time !== '') {
        this.newTask.title = title;
        this.newTask.time = time;
        this.tasks.push(this.newTask);
        this.newTask = {
          title: '',
          time: 0
        };
      } else {
        alert('La tarea y el tiempo deben tener un valor');
      }

      localStorage.setItem('tasks', JSON.stringify(this.tasks));
    },

    deleteTask(task) {
      const index = this.tasks.indexOf(task);
      this.tasks.splice(index, 1);
      localStorage.setItem('tasks', JSON.stringify(this.tasks));
    }
  },

  computed: {
    totalTime() {
      let total = 0;
      this.tasks.forEach(task => {
        total += parseInt(task.time);
      });
      return total;
    }
  },

  created() {
    const tasks = localStorage.getItem('tasks');
    if (tasks) {
      this.tasks = JSON.parse(tasks) || [];
    }
  }
};
</script>

<style lang="scss">

</style>

Este es mi prototipo 100% funcional ademas le añadí un plus con las actividades completada y una barra de progreso para saber como vamos.

HTML

<template lang="pug">
  #app
    .columns
      .column
      .column
        h1 Tareas y horas
        br
        input.input.is-8(type="text" v-model="title" name="title" placeholder="title" )
        input.input.is-8(type="text" v-model="time" name="time" placeholder="time" ) 
        input.button.link(type="submit" name="submit" @click="addTask(title,time)")
        p(v-if="totalTime!==0") {{totalTime}}
        div(v-for="(task,i) in tasks")
          ul
            li {{task.title}} - {{task.time}}   
            button.delete.is-medium(@click="removeTask(i)")
            br
      .column
</template>

JS

<script>
export default {
  name: 'app',
  
  data () {
    return {
      title:'',
      time:0,
      tasks:[]
    }
  },

  computed:{
    totalTime () {
      let total = 0
      this.tasks.map(function(sum){
        total += sum.time
      })
      return total
    }
  },
  created(){
    this.tasks = JSON.parse(localStorage.getItem('tasks')) || []
  },
  methods:{
    addTask (title, time) {
      if(title, time){
        const newTask ={
          title,
          time : parseInt(time)        
        }
        this.tasks.push(newTask);
      }else(
        alert('debe llenar todos los campos')
      )
      localStorage.setItem('tasks',JSON.stringify(this.tasks))
      this.cancel ()
    },
    cancel () {
      this.title = '',
      this.time = ''
    },
    removeTask (i) {
      let remove = this.tasks.splice(i,1);
      localStorage.setItem('tasks',JSON.stringify(this.tasks))
      return remove
    } 
  }
}
</script>

Mi frontend no es muy bueno, pero aquí va, sigo usando bulma, no me gustó que no tiene clases como tan practicas en comparación de bootstrap para listas y hay que desarrollar mas del lado del css, pero bueno, la funcionalidad está.

<template lang="pug">
  #app
    section.section
      nav.nav.has-shadow
        .container
          .field
            label.label Título de la tarea
              .control
                input.input.is-large(type="text", 
                                      placeholder="Título de la tarea" 
                                      v-model="title")
          .field
            label.label Tiempo de trabajo
              .control
                input.input.is-large(type="text", 
                                      placeholder="Tiempo trabajado" 
                                      v-model="time")
          .field.is-grouped.is-grouped-right
            .control
              a.button.is-info.is-large(@click="addTask") Agregar Tarea
              a.button.is-danger.is-large(@click="deleteTask") &times; Eliminar Lista completa
          br
          br
          h2 {{ timeWork }}
          p 
            small {{ numberTask }}
          
      .container
        .ul
          .li(v-for="(t, index) in tasks") {{ t.title }} - {{ t.time }}
            a.button.is-danger(@click="deleteThisTask(index)") &times;
        p.error 
          small {{ mensajeError }}

</template>

<script>
const tasks = []
export default {
  name: 'app',
  created(){
    this.tasks = JSON.parse(localStorage.getItem(tasks)) || []
  },
  data () {
    return {
      title: '',
      time: '',
      tasks: [],
      mensajeError: '',
      indexList: ''
    }
  },
  computed:{
    numberTask(){
      return `Tareas Pendientes: ${this.tasks.length}`
    },
    timeWork(){
      let horasTrabajadas = 0
      for(let item of this.tasks){
        horasTrabajadas += Number(item.time)
      }
      return `Hasta el momento hemos trabajado ${horasTrabajadas} hrs.`
    }
  },
  methods:{
    addTask(){
      if(this.title === '' || this.time === ''){
        this.mensajeError = 'Debe indicar los dos elementos título y horario'
      }else{
        if (!/^([0-9])*$/.test(this.time)){
          this.mensajeError = 'El tiempo debe ser un número'
        }else{
          this.tasks.push({title: this.title, time: this.time})
          this.title = ''
          this.time = ''
          this.mensajeError = ''
          localStorage.setItem(tasks, JSON.stringify(this.tasks))
        }
      }
    },
    deleteTask(){
      this.tasks = []
      localStorage.setItem(tasks, JSON.stringify(this.tasks))
    },
    deleteThisTask(index){
      this.tasks.splice(index, 1)
      localStorage.setItem(tasks, JSON.stringify(this.tasks))
    }
  }
}
</script>

<style lang="scss">
  @import './scss/main.scss';
  .results {
    margin-top: 15px;
  }
  .error {
    color: red;
    font-weight: bolder;
  }
  .button {
    margin-right: 10px;
  }
</style>

Originalmente lo hice con html porque no me gusta pug pero lo converti a pug para ustedes

.conteiner
  nav.nav.justify-content-center.p-3.mb-2.bg-info.text-white
    a.nav-link.text-dark(href='#') Home
  .conteiner
    | {{ name }}
  .conteiner
    .col-12
      .d-flex.justify-content-center
        .form-group
          label(for='') Name
          input.form-control(type='text' placeholder='Add Name' v-model='title')
          small.form-text.text-muted Add the new Title Name
        .d-flex.justify-content-center
          .form-group
            label(for='') Time
            input.form-control(type='text' placeholder='Add time' v-model='time')
            small.form-text.text-muted Add the new time Name
          button.btn.btn-primary(type='button' v-on:click='addTask()' data-toggle='modal' data-target='#exampleModal')
            | Add
          button.btn.btn-danger(type='button' v-on:click='restartTask()' data-toggle='modal' data-target='#exampleModal')
            | cancel
  table.table
    thead
      tr
        th Title
        th Time
    tbody
      tr(v-for='(item, index) in task' :key='item')
        td(scope='row') {{ item.title }}
        td(scope='row') {{ item.time }}
        td(scope='row')
          button.btn.btn-danger(type='button' v-on:click='remove(index)' data-toggle='modal' data-target='#exampleModal')
            | &times;
    p.text-success.totalTime {{ totalTime }}
  #exampleModal.modal.fade(v-if='showMessage' tabindex='-1' role='dialog' aria-labelledby='exampleModalLabel' aria-hidden='true')
    .modal-dialog(role='document')
      .modal-content
        .modal-header
          h5.modal-title Modal title
          button.close(type='button' data-dismiss='modal' aria-label='Close')
            span(aria-hidden='true') &times;
        .modal-body
          p {{ message }}```

Mi codigo de la solucion al reto:

<!-- Use preprocessors via the lang attribute! e.g. <template lang="pug"> -->
<template>
  <div id="app">
    <h3>Coolest Vue ToDo's App</h3>
    <div class="searchContainer">
      <input type="text" />
      <button>Search ToDo</button>
    </div>
    <main class="main-container">
      <div class="todosList">
        <h1>All ToDo's</h1>
        <ul>
          <li class="single-todo" v-for="t in todos">
            <p>Todo: {{ t.title }}</p>
            <p>When: {{ t.time }}</p>
            <span @click="deleteTodo(t.id)">Delete this ToDo &times;</span>
          </li>
        </ul>
      </div>
      <div>
        <h4>Add new todo</h4>
        <form @submit.prevent="handleAddTodo" class="todo-form">
          <input v-model="todoTitle" type="text" placeholder="ToDo Title" />
          <br />
          <br />
          <input v-model="todoTime" type="number" />
          <br />
          <br />
          <button class="submit-button">Add New ToDo</button>
        </form>
      </div>
    </main>
  </div>
</template>

<script>
const dummyTodos = [
  {
    title: "Walk the dog",
    time: 3,
    id: Math.random() * 1000000
  },
  {
    title: "Buy eggs",
    time: 5,
    id: Math.random() * 1000000
  },
  {
    title: "Message mom",
    time: 8,
    id: Math.random() * 1000000
  }
];

export default {
  data() {
    return {
      todos: dummyTodos,
      todoTitle: "",
      todoTime: 0
    };
  },
  computed: {
    handleTodo() {
      return {
        title: this.todoTitle,
        time: this.todoTime,
        id: Math.random() * 1000000
      };
    }
  },
  methods: {
    handleAddTodo() {
      this.todos.push(this.handleTodo);
    },
    deleteTodo(id) {
      this.todos = this.todos.filter((item) => item.id !== id);
    }
  }
};
</script>

<!-- Use preprocessors via the lang attribute! e.g. <style lang="scss"> -->
<style>
* {
  font-family: sans-serif;
}
h3 {
  text-align: center;
}
.searchContainer {
  display: flex;
  justify-content: center;
}

.todosList {
  margin-top: 50px;
  width: 300px;
}

.todosList ul {
  list-style-type: none;
}

.todosList h1 {
  text-align: center;
}

.single-todo {
  border: 1px solid black;
  color: whitesmoke;
  padding-left: 5px;
  padding-bottom: 5px;
  margin-bottom: 10px;
  border-radius: 8px;
  background-color: #2b9eb1;
}

.single-todo span {
  color: red;
  margin-left: 110px;
}

.main-container {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 50px;
}

.todo-form {
  width: 300px;
}

.submit-button {
  margin-left: 65px;
}
</style>
<template lang="pug" >
#home
  .container.mio
    .columns
      .column.is-6.is-offset-3.is-child.box
        p.has-text-black.has-text-weight-bold.pb-4 Lista de Actividades
          .field.is-horizontal
            .field-label.is-normal
            label.labelmio Nombre Actividad:
            .field-body
              .field
                .control
                  input.input(placeholder="task", v-model="newTask.title")
          .field.is-horizontal
            .field-label.is-normal
            label.labelmio Tiempo ejecucion:
            .field-body
              .field
                .control
                  input.input(placeholder="time", v-model="newTask.time", type="number")
                  button.button.buttonmio(@click="addTasks") Agregar
                  button.button.buttonmio(@click="cancel") Cancelar
    .columns
      .column.is-8.is-offset-2.box.mio
        p(v-show="!tasks.length") Aun no hay tareas cargadas
        p(v-show="tasks.length").has-text-black.has-text-weight-bold Actividades realizadas
          p(v-for="t in tasks") Actividad: {{ t.title }} Tiempo de ejecucion: {{ t.time }}
            button.button.buttonmio2.is-danger.is-outlined(@click="removeTask(t)")
              span.icon.is-small
                i.fas.fa-times
          p Total tiempo {{ totalTime }}
</template>
<script>
export default {
  name: 'Home',
  data () {
    return {
      name: '',
      tasks: [],
      newTask: {
        title: '',
        time: 0
      }
    }
  },
  methods: {
    cancel () {
      this.newTask.title = this.newTask.time = ''
    },
    addTasks () {
      var copi = Object.assign({}, this.newTask)
      this.tasks.push(copi)
      this.cancel()
      localStorage.setItem('tasks', JSON.stringify(this.tasks))
    },
    removeTask (task) {
      var i = this.tasks.indexOf(task)
      console.log(i)
      if (i !== -1) {
        this.tasks.splice(i, 1)
      }
      localStorage.setItem('tasks', JSON.stringify(this.tasks))
    }
  },
  computed: {
    totalTime () {
      var suma = 0
      for (var t = 0; t < this.tasks.length; t++) {
        const element = this.tasks[t].time
        suma = suma + parseInt(element)
      }
      return suma
    }
  },
  created () {
    this.tasks = JSON.parse(localStorage.getItem('tasks')) || []
  },
  components: {}
}
</script>
<style scoped>
.mio{
  background: white;
  margin-top: 50px;
}
.labelmio{
  margin-left: -70px;
  margin-right: 30px;
}
.buttonmio{
  margin-top: 20px;
  }
  .buttonmio2{
    margin-left: 90px;
    padding: 20px;
    width: 15px;
  }
</style>```

Done! :3

<template>

  <h1>Nombre: {{ name }}</h1>

  <input type="text" v-model="newTask.title" placeholder="Nombre de la tarea">
  <input type="text" v-model="newTask.time" placeholder="Tiempo de la tarea">
  <button @click="addTask">Añadir tarea</button>

  <div v-show="tasks.length > 0">

    <ul>
      <li v-for="(task, i) in tasks" :key="i">
        <b>Nombre de la tarea:</b> {{ task.title }} 
        <b>Tiempo:</b> {{ task.time }} 
        <button class="is-danger" @click="removeTask(i)">&times;</button>
      </li>
    </ul>

    <p>Se han trabajado {{ totalTime }} horas</p>

  </div>

  <p v-show="tasks.length <= 0">No hay tareas para mostrar</p>

</template>

<script>
export default {
  name: 'App',
  components: {},
  created() {
    this.tasks = JSON.parse(localStorage.getItem("tasks")) || [];
  },
  data() {
    return {
      name: "",
      tasks: [],
      newTask: {
        title: "",
        time: ""
      }
    }
  },
  methods: {
    saveInLocalSotrage() {
      localStorage.setItem("tasks", JSON.stringify(this.tasks));
    },
    addTask() {
      const task = this.newTask;
      if (task.title != "" && task.time != "") {
        
        this.tasks.push({
          title: task.title,
          time: task.time
        });
        task.title = "";
        task.time = "";
        this.saveInLocalSotrage();
      }
    },
    removeTask(index){ 
      this.tasks.splice(index, 1);
      this.saveInLocalSotrage();
    }
  },
  computed: {
    totalTime() {
      let hours = 0;
      const tasks = this.tasks;
      for (const task of tasks)
        hours += parseInt(task.time);
      return hours;
    }
  }
}
</script>

<style lang="scss">
/* Es una buena pŕactica importar los estiulos generales desde el componente App.vue */
@import "./scss/main.scss";
</style>

Les presento mi práctica:

<template lang="pug">
.container.is-fluid
  section.section
    center
        img(src="../../assets/images/logo_azael.png", width="150")
    h1.is-size-2.tasks-title.has-text-centered.is-paddingless Administrador de Tareas
    br
    .columns
      .column.is-6.is-offset-one-quarter
        .card
          header.card-header
            h1.card-header-title
              | Crear Tarea Nueva
          .card-content
            .content
              .notification.is-warning(v-show="warning")
                |Debe completar los campos correctamente
                button.delete(@click="warning = false") 
              .field
                label.label Tarea:
                p.control.has-icons-left
                  input.input(
                      type='text', 
                      placeholder='Nombre de tu tarea',
                      v-model="newTask.task")
                  span.icon.is-small.is-left
                    i.fa.fa-clipboard-check
              .field
                label.label Horas:
                p.control.has-icons-left
                  input.input(
                      type='number',
                      placeholder='Tiempo invertido',
                      v-model="newTask.time")
                  span.icon.is-small.is-left
                    i.fa.fa-stopwatch
              
              button.button.is-primary.addTask(@click="addTask") Agregar
              button.button(@click="cancel") Cancelar

    .columns
      .column.is-6.is-offset-one-quarter
        .card
          header.card-header
            h1.card-header-title
              | Listado de Tareas
          .card-content
            .content.table_wrapper
                table.table.has-text-centered
                    thead
                        tr
                            th Nombre tarea
                            th Tiempo Invertido
                            th Eliminar
                    tbody
                        tr(v-show="alert")
                            td.has-text-centered(colspan="3", v-if="!tasks.length")
                                | No se encontraron tareas registradas                            
                        tr(v-for="t in tasks")  
                            td {{ t.task }}
                            td {{ t.time }}
                            td
                                button.button.is-danger(@click="deleteTask(t)")
                                    i.fa.fa-times                            
          footer.card-footer
            p.card-footer-item
                span 
                    strong {{ `Total de Horas: ${totalTime} ` }}                                    
</template>

<script>
export default {
    name: "tasks",
    data () {
        return {
            alert: true,
            warning: false,
            newTask: { task : '', time : 0 },
            tasks: [],
            sumaHoras: 0
        }
    },
    methods:{
        addTask () {
            const self = this;

            if (self.newTask.task == '' || self.newTask.time == 0 ) {
                self.warning = true
                return false
            } else {
                self.warning = false
            }  

            if (self.tasks.length > 0) {
                self.alert = false
            }else{
                self.alert = true  
            }
            
            self.tasks.push(self.newTask); 
            this.cancel();   
        },
        cancel () {
            const self = this;
            self.newTask = {task : '', time : 0}
        },
        deleteTask (task) {
            const self = this;
            let index  = self.tasks.indexOf(task);
            self.tasks.splice(index, 1)
        }
    },
    computed:{
        totalTime () {
            const self  = this;
            let suma = 0;

            self.tasks.forEach(task => {
                suma = parseInt(suma) + parseInt(task.time)
            });

            return suma;
        }
    }     
}
</script>

<style scoped>
.table_wrapper{
    overflow-x: auto;   
}
.addTask{
    margin:0 10px;
}
</style>

Comparto mi ejercicio, me costó trabajo por que lo hice sin bootstrap
https://codepen.io/carlos-fuentes-the-selector/pen/WWQxYK

Listo mi ejercicio, dejo aquí mi Codepen

https://codepen.io/jgorocica/full/OGpEop

Saludos

Aquí mi código

<template lang="pug">
  #container
    h2 {{ name }}
    .columns
      .column.is-4
        .field
          label.label.labelLeft(for="name") Nombre
          input.input.is-small(type="text" v-model:name="newTask.name" id="name")
        .field
          label.label.labelLeft(for="time") Tiempo
          input.input.is-small(type="text" v-model:name="newTask.time" id="time")
        .buttonLayout
          button.button.is-success.buttonLeft(@click="addTask") Crear tarea
          button.button.is-info.buttonLeft(@click="cancel") Cancelar
      .column.is-8
        div.table
          table(v-show="tasks.length")
            tr
              td Nombre
              td Titulo
              td
            tr(v-for="(task, index) in tasks")
              td {{task.name}}
              td {{ task.time }}
              td
                button.button.is-danger(@click="removeTask(index)" ) X
          h2(v-show="!tasks.length") no hay ninguna tarea cargada
    p.message.is-danger.message-body.center(v-show="tasks.length") Tiempo total de trabajo {{ totalTime }}

</template>

<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
      name: 'Servicio de tareas',
      tasks: [],
      newTask: {
        name: '',
        time: 0
      }
    }
  },
  created () {
    this.tasks = JSON.parse(localStorage.getItem('tasks')) || []
  },
  methods: {
    addTask () {
      if (this.newTask.name === '' || this.newTask.time === 0) {
        return
      }
      let name = this.newTask.name
      let time = this.newTask.time
      this.tasks.push({
        name,
        time
      })
      localStorage.setItem('tasks', JSON.stringify(this.tasks))
    },
    cancel () {
      this.newTask.name = ''
      this.newTask.time = 0
    },
    removeTask (index) {
      this.tasks.splice(index, 1)
      localStorage.setItem('tasks', JSON.stringify(this.tasks))
    }
  },
  computed: {
    totalTime () {
      let timeTotal = 0
      for (let i in this.tasks) {
        timeTotal += parseInt(this.tasks[i].time)
      }
      return timeTotal
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="scss" scoped>
@import '~bulma/bulma.sass';
table {
  width: 100%;
  margin-left: 5%;
  margin-right: 5%;
}

.inputLayout {
  display: inline;
}
h2 {
  margin-top: 5%;
  margin-bottom: 5%;
}
.labelLeft{
  text-align: left;
}
.buttonLeft{
  margin-left: 5%;
}
.center {
  text-align: center;
}
</style>

<template>
  <div id="app">
    <section class="section">
      <nav class="nav has-shadow">
        <div class="container">
          <div class="field has-addons">
            <div class="control is-expanded">
              <input
                v-model="newTask.title" 
                type="text" 
                placeholder="Titulo" 
                class="input is-large">
            </div>
            <div class="control is-expanded">
              <input
                v-model="newTask.time" 
                type="number" 
                placeholder="Tiempo" 
                class="input is-large">
            </div>
            <div class="control">
              <a class="button is-info is-large" @click="addTask">Add Task</a>
            </div>
            <div class="control">
              <a class="button is-danger is-large" @click="cancel">&times;</a>
            </div>
          </div>
          <p><small>{{ totalTime }}</small></p>
        </div>
        <div class="container">
          <p>{{ name }}</p>
        </div>
        <div class="container results">
          <div class="columns" >
            <template v-if="tasks.length != 0">
              <div class="column" v-for="(t, i) in tasks">
                <p>{{ t.title }} - {{ t.time }}</p>
                <br>
                <a class="button is-danger is-large" @click="removeTask(i)">&times;</a>
              </div>
            </template>
            <template v-else>
              <p>No hay tasks</p>
            </template>
          </div>
        </div>
      </nav>
    </section>
  </div>
</template>

<script>

export default {
  name: 'app',
  data () {
    return {
      name: '',
      tasks: [],
      newTask: {
        tilte: '',
        time: 0
      }
    }
  },
  created: function () {
    this.tasks = JSON.parse(window.localStorage.getItem('tasks')) || []
  },
  computed: {
    totalTime () {
      var total = 0
      for (var i = 0; i < this.tasks.length; i++) {
        total = parseInt(total) + parseInt(this.tasks[i].time)
      }
      return `Tiempo total: ${total}`
    }
  },
  methods: {
    removeTask (i) {
      this.tasks.splice(i)
      window.localStorage.setItem('tasks', JSON.stringify(this.tasks))
    },
    cancel () {
      this.newTask = {
        tilte: '',
        time: 0
      }
    },
    addTask () {
      if (this.newTask.title && this.newTask.time) {
        this.tasks.push({
          title: this.newTask.title,
          time: this.newTask.time
        })
      }
      this.newTask = {
        tilte: '',
        time: 0
      }
      window.localStorage.setItem('tasks', JSON.stringify(this.tasks))
    }
  }
}
</script>

<style lang="scss">
  @import './assets/scss/main.scss';

  .results{
    margin-top: 50px;
  }
</style>

Adjunto mi aporte

<template lang ="pug">
  #app
    section.section
      nav.nav.has-shadow
        .container
          h1.title.is-1.has-text-centered Gestor de Tareas
          .container(v-show="!name")
              .field.is-3.has-addons
                .control
                  .columns
                    .column
                      input#name.input.is-large(type="text", placeholder="Ingresa tu nombre", v-model="newName")
                .control
                  .columns
                    .column
                      a.button.is-primary.is-large(type="submit", @click="addName") Ok

      h2.title.is-4(v-show="name") Tareas de {{ name }}
      hr
      .columns(v-show="name")
        .column
          h2.title.is-3.has-text-centered Agregar Tarea
          .columns.is-mobile
            .column.is-three-quarters
              .field
                .control
                  input.input(type="text", v-model="task.title", placeholder="Titulo de Tarea")
            .column
              .field
                .control
                  input.input.is-primary(type="number", placeholder="0", v-model="task.time")
                hr
                a.button.is-primary(@click="addTask") +
                a.button.is-danger(@click="cancel") x
        .column
          h2.title.is-3.has-text-centered Lista de Tareas
          p.has-text-centered(v-show="!tasks.length") Aun no hay tareas cargadas
          div(v-show="tasks.length")
            ol
              li.has-text-centered(v-for="t in tasks")

                  .columns
                    .column
                      .tags.has-addons
                        p.tag.is-medium.is-info {{ t.title }}
                        span.tag.is-medium.is-warning {{ t.time }}
                    .column
                      button.button.tag.is-danger(@click="removeTask") eliminar

        .column
          h2.title.is-3.has-text-centered Tiempo Requerido
            .tags.has-addons.has-text-centered
              span.tag.is-medium.is-warning {{ totalTime }}
              span.tag.is-medium.is-medium Horas






    </section>
</template>

<script>
export default {
  name: 'app',
  data () {
    return {
      newName: null,
      name: '',
      tasks: [],
      task: {
        title: '',
        time: 0
      }
    }
  },
  created () {
    this.tasks = JSON.parse(window.localStorage.getItem('tasks')) || []
    this.name = JSON.parse(window.localStorage.getItem('name'))
  },
  computed: {
    totalTime () {
      if (!this.tasks.length) {
        return 0
      }
      let total = 0
      this.tasks.forEach(t => {
        total += parseInt(t.time)
      })
      return total
    }
  },
  watch: {
  },
  methods: {
    addName () {
      this.name = this.newName
      window.localStorage.setItem('name', JSON.stringify(this.name))
    },
    addTask () {
      if (!this.task.title || !this.task.time) {
        return
      }
      this.tasks.push({
        title: this.task.title,
        time: this.task.time
      })
      this.task.title = ''
      this.task.time = 0
      window.localStorage.setItem('tasks', JSON.stringify(this.tasks))
    },
    cancel () {
      this.task.title = ''
      this.task.time = 0
    },
    removeTask (index) {
      this.tasks.splice(index, 1)
      window.localStorage.setItem('tasks', JSON.stringify(this.tasks))
    }
  }
}
</script>

<style lang="scss">
  @import './scss/main.scss';
  html {
  padding: 20px;
  color: #3d3d3d;
  }
</style>

Les dejo mi ejercicio: https://codepen.io/ludwingperezt/pen/gEWPGx

No agregué estilos porque quería enfocarme en la solución del código con vue. Ahora que veo lo que hicieron ustedes me da vergüenza como dejé el ejercicio, probablemente lo haga luego jejeje

<template>
  <div id="app">
    <div class="container is-fluid">
      <nav class="level">
        <div class="level-left">
          <div class="level-item">
            <div class="field">
              <div class="control">
                <input class="input is-info" type="text" placeholder="Titulo de la tarea" v-model="newTasks.value">
              </div>
            </div>
          </div>
          <div class="level-item">
            <div class="field">
              <div class="control">
                <input class="input is-primary" type="number" placeholder="Tiempo de la tarea" v-model="newTasks.time">
              </div>
            </div>
          </div>
          <div class="level-item">
            <a class="has-text-centered button is-link is-outlined is-medium" v-on:click="addTask(newTasks)">Añadir Tarea</a>
          </div>
          <div class="level-item">
            <a class="has-text-centered button is-primary is-outlined is-medium" v-on:click="cancelar">Cancelar</a>
          </div>
          <div class="level-item" v-show="!mostrar">
            <article class="message is-danger">
              <div class="message-body">
                Error el titulo no debe de estar vacio y el tiempo de la tarea debe de ser mayor a "0".
              </div>
            </article>
          </div>
        </div>
        <div class="level-right">
          <div class="level-item">
            <p class="has-text-centered">
              <a class="link is-info">{{name}}</a>
            </p>
          </div>
        </div>
      </nav>
      <div v-show="tasks != ''">
        <article class="media" v-for="task in tasks" :key="task">
          <figure class="media-left">
            <div class="page__toggle">
              <label class="toggle">
                <input class="toggle__input" type="checkbox" v-if="task.check" checked="checked">
                <input class="toggle__input" type="checkbox" v-else>
                <span class="toggle__label">
                  <span class="toggle__text"></span>
                </span>
              </label>
            </div>
          </figure>
          <div class="media-content">
            <div class="content">
              <div class="columns is-multiline is-variable is-1">
                <div class="column is-two-thirds">
                  <p class="notification is-info">{{task.value}}</p>
                </div>
                <div class="column">
                  <p class="notification is-primary">{{task.time}}</p>
                </div>
              </div>
            </div>
          </div>
          <div class="media-right">
            <button class="delete" v-on:click="removeTask(task)"></button>
          </div>
        </article>
        <div class="media columns is-multiline is-variable is-1">
          <div class="column is-full">
            <p class="notification is-link">{{totalTimeResult}}</p>
          </div>
        </div>
      </div>
      <article class="media" v-show="tasks == ''">
        <div class="column is-full">
          <p class="notification is-warning">No existe ninguna tarea</p>
        </div>
      </article>
    </div>
  </div>
</template>
<script>
const tracks = [
  { id: 1, name: 'Muchacha', artist: 'Luis Alberto Spinetta' },
  { id: 2, name: 'Hoy aca en el baile', artist: 'El Pepo' },
  { id: 3, name: 'I was made for loving you', artist: 'Kiss' }
]

export default {
  name: 'app',

  data () {
    return {
      name: 'Angel Infanti',
      rayas: 16,
      mostrar: true,
      tasks: [],
      newTasks: {
        value: '',
        time: 0,
        check: true
      }
    }
  },

  created () {
    this.tasks = JSON.parse(localStorage.getItem('tasks')) || []
  },
  computed: {
    searchMessage () {
      return `Encontrados: ${this.tracks.length}`
    },
    totalTimeResult () {
      var totalTime = 0
      for (var recorrido = 0; recorrido < this.tasks.length; recorrido++) {
        console.log(this.tasks[recorrido].time)
        totalTime = totalTime + this.tasks[recorrido].time
      }
      return `Tiempo total de ectividades: ${totalTime}`
    }
  },

  methods: {
    addTask (newTasks) {
      newTasks.time = parseInt(newTasks.time)
      if (newTasks.value !== '' && newTasks.time > 0) {
        this.tasks.push(newTasks)
        this.newTasks = {
          value: '',
          time: 0,
          check: true
        }
        this.mostrar = true
        localStorage.setItem('tasks', JSON.stringify(this.tasks))
      } else {
        this.mostrar = false
      }
    },
    removeTask (indice) {
      this.tasks.splice(indice, 1)
      localStorage.setItem('tasks', JSON.stringify(this.tasks))
    },
    cancelar () {
      this.newTasks = {
        value: '',
        time: 0,
        check: false
      }
    },
    search () {
      this.tracks = tracks
    }
  }
}
</script>

<style lang="scss">@import './scss/main.scss'</style>
<style type="scss">
  .toggle{
    --uiToggleSize: var(--toggleSize, 20px);
    --uiToggleIndent: var(--toggleIndent, .4em);
    --uiToggleBorderWidth: var(--toggleBorderWidth, 2px);
    --uiToggleColor: var(--toggleColor, #000);
    --uiToggleDisabledColor: var(--toggleDisabledColor, #868e96);
    --uiToggleBgColor: var(--toggleBgColor, #fff);
    --uiToggleArrowWidth: var(--toggleArrowWidth, 2px);
    --uiToggleArrowColor: var(--toggleArrowColor, #fff);

    display: inline-block;
    position: relative;
  }
  .toggle__input{
    position: absolute;
    left: -99999px;
  }
  .toggle__label{
    display: inline-flex;
    cursor: pointer;
    min-height: var(--uiToggleSize);
    padding-left: calc(var(--uiToggleSize) + var(--uiToggleIndent));
  }
  .toggle__label:before, .toggle__label:after{
    content: "";
    box-sizing: border-box;
    width: 1em;
    height: 1em;
    font-size: var(--uiToggleSize);

    position: absolute;
    left: 0;
    top: 0;
  }
  .toggle__label:before{
    border: var(--uiToggleBorderWidth) solid var(--uiToggleColor);
    z-index: 2;
  }

  .toggle__input:disabled ~ .toggle__label:before{
    border-color: var(--uiToggleDisabledColor);
  }

  .toggle__input:focus ~ .toggle__label:before{
    box-shadow: 0 0 0 2px var(--uiToggleBgColor), 0 0 0px 4px var(--uiToggleColor);
  }

  .toggle__input:not(:disabled):checked:focus ~ .toggle__label:after{
    box-shadow: 0 0 0 2px var(--uiToggleBgColor), 0 0 0px 4px var(--uiToggleColor);
  }

  .toggle__input:not(:disabled) ~ .toggle__label:after{
    background-color: var(--uiToggleColor);
    opacity: 0;
  }

  .toggle__input:not(:disabled):checked ~ .toggle__label:after{
    opacity: 1;
  }

  .toggle__text{
    margin-top: auto;
    margin-bottom: auto;
  }

  /*
  The arrow size and position depends from sizes of square because I needed an arrow correct positioning from the top left corner of the element toggle
  */

  .toggle__text:before{
    content: "";
    box-sizing: border-box;
    width: 0;
    height: 0;
    font-size: var(--uiToggleSize);

    border-left-width: 0;
    border-bottom-width: 0;
    border-left-style: solid;
    border-bottom-style: solid;
    border-color: var(--uiToggleArrowColor);

    position: absolute;
    top: .5428em;
    left: .2em;
    z-index: 3;

    transform-origin: left top;
    transform: rotate(-40deg) skew(10deg);
  }

  .toggle__input:not(:disabled):checked ~ .toggle__label .toggle__text:before{
    width: .5em;
    height: .25em;
    border-left-width: var(--uiToggleArrowWidth);
    border-bottom-width: var(--uiToggleArrowWidth);
    will-change: width, height;
    transition: width .1s ease-out .2s, height .2s ease-out;
  }

  /*
  =====
  LEVEL 2. PRESENTATION STYLES
  =====
  */
  /*
  The demo skin
  */
  .toggle__label:before, .toggle__label:after{
    border-radius: 2px;
  }
  /*
  The animation of switching states
  */
  .toggle__input:not(:disabled) ~ .toggle__label:before,
  .toggle__input:not(:disabled) ~ .toggle__label:after{
    opacity: 1;
    transform-origin: center center;
    will-change: transform;
    transition: transform .2s ease-out;
  }

  .toggle__input:not(:disabled) ~ .toggle__label:before{
    transform: rotateY(0deg);
    transition-delay: .2s;
  }

  .toggle__input:not(:disabled) ~ .toggle__label:after{
    transform: rotateY(90deg);
  }

  .toggle__input:not(:disabled):checked ~ .toggle__label:before{
    transform: rotateY(-90deg);
    transition-delay: 0s;
  }

  .toggle__input:not(:disabled):checked ~ .toggle__label:after{
    transform: rotateY(0deg);
    transition-delay: .2s;
  }

  .toggle__text:before{
    opacity: 0;
  }

  .toggle__input:not(:disabled):checked ~ .toggle__label .toggle__text:before{
    opacity: 1;
    transition: opacity .1s ease-out .3s, width .1s ease-out .5s, height .2s ease-out .3s;
  }

  /*
  =====
  LEVEL 3. SETTINGS
  =====
  */

  .toggle{
    --toggleColor: #690e90;
    --toggleBgColor: #9b59b6;
    --toggleSize: 50px;
  }
</style>

Sigo construyendo mis interfaces de usuario con Vuetify. Este es el resultado final:

A continuación presento el código:

<template lang="pug">
v-app
  v-content
    v-container(fluid)
      v-layout(align-center justify-center)
        v-flex(xs12 sm8 md6 lg5)
          v-card.elevation-12
            v-toolbar(color="secondary" dark card prominent)
              v-toolbar-title.headline.text-uppercase
                span.font-weight-light {{ name }}
            v-card-text
              form(@submit.prevent="addTask")
                v-container.pa-0(fluid)
                  v-layout(grid-list-xs wrap)
                    v-flex.px-1(xs12 sm8 md9 lg10)
                      v-text-field(v-model="newTask.title" label="Título")
                    v-flex.px-1(xs12 sm4 md3 lg2)
                      v-text-field(v-model="newTask.time" label="Tiempo (min.)" type="number" min="1")
                    v-flex.px-1(xs6)
                      v-btn(type="submit" color="primary" block) Add Task
                    v-flex.px-1(xs6)
                      v-btn(@click="cancel" block) Cancel
            v-divider
            v-list(two-line)
              v-subheader Tareas
                span.ml-1.font-weight-light (Tiempo total: {{ totalTime }} hrs.)
              template(v-if="tasks.length > 0")
                v-list-tile(v-for="(task, idx) in tasks" :key="idx" @click="")
                  v-list-tile-content
                    v-list-tile-title {{ task.title }}
                    v-list-tile-sub-title Tiempo: {{ task.time }} min.
                  v-list-tile-action
                    v-btn(@click="removeTask(idx)" color="error" flat fab)
                      v-icon delete
              v-list-tile(v-else)
                v-list-tile-content
                  v-list-tile-title.text-xs-center No tasks
  v-snackbar(v-model="msg.show" :color="msg.color" :timeout="3000" bottom multi-line dark) {{ msg.text }}
</template>

<script>
export default {
  name: 'App',
  created () {
    this.tasks = JSON.parse(localStorage.getItem('tasks')) || []
  },
  data () {
    return {
      msg: {
        show: false,
        text: '',
        color: ''
      },
      name: 'Leonardo Campo R.',
      tasks: [
      ],
      newTask: {
        title: '',
        time: 0
      }
    }
  },
  methods: {
    addTask () {
      if (!this.newTask.title || !this.newTask.time) {
        this.showMsg('error', 'Debe ingresar un título y el tiempo')
        return
      }
      this.tasks.push({ title: this.newTask.title, time: parseInt(this.newTask.time) })
      this.save()
      this.cancel()
    },
    removeTask (idx) {
      this.tasks.splice(idx, 1)
      this.save()
    },
    cancel () {
      this.newTask.title = ''
      this.newTask.time = 0
    },
    save () {
      localStorage.setItem('tasks', JSON.stringify(this.tasks))
    },
    showMsg (color, text) {
      this.msg.text = text
      this.msg.color = color
      this.msg.show = true
    }
  },
  computed: {
    totalTime () {
      let time = 0
      this.tasks.forEach((task) => {
        time += task.time
      })
      return parseFloat((time / 60.0).toFixed(2))
    }
  }
}
</script>

Ejercicio:

<template lang="pug">
  #app
    .columns
      .column {{ name }}
        h1.title.has-background-info(v-if="!comprobarLista") las horas que debes invertir para ser un poco menos ignorante son: {{ totalTime }}
        h1.title.has-background-warning(v-else) No hay tareas que mostrar
      .column
        h1 Agrega una nueva Tarea
        input.input(v-model="newTask.title")
        span(v-show="mensajeTitle").tag.is-warning.is-large El texto no es valido
        input.input(v-model="newTask.time")
        span(v-show="mensajeTime").tag.is-warning.is-large ingresa un número valido
        br
        br
        button.button(@click="addTask") Agregar tarea
        button.button.is-danger(@click="cancelTask") cancelar Tarea
    .columns
      .column
        h1 Tus Tareas Pendientes son
        ul
          li(v-for="(task, index) in tasks")
            span.tag.is-success {{index}} {{ task.title }}, Tiempo Estimado {{ task.time }} horas
              button.delete(@click="deleteTask($event, index)")
</template>

<script>

export default {
  name: 'app',
  created () {
    this.tasks = JSON.parse(localStorage.getItem('tasks')) || []
  },
  data () {
    return {
      name: 'HERMESTO',
      tasks: [
        {
          title: 'titulo 1',
          time: 2
        },
        {
          title: 'titulo 2',
          time: 5
        }
      ],
      newTask: {
        title: '',
        time: 0
      },
      mensajeTitle: false,
      mensajeTime: false
    }
  },
  computed: {
    totalTime () {
      let total = 0
      for (let i = 0; i < this.tasks.length; i++) {
        total += parseInt(this.tasks[i].time)
      }
      return total
    },
    comprobarLista () {
      if (this.tasks.length === 0) {
        return true
      } else {
        return false
      }
    }
  },
  methods: {
    addTask () {
      if (this.newTask.title.trim().length === 0) {
        this.mensajeTitle = true
        return false
      } else {
        this.mensajeTitle = false
      }
      if (parseInt(this.newTask.time) > 0) {
        this.mensajeTime = false
      } else {
        this.mensajeTime = true
        console.log('falio')
        return false
      }
      let tmpTask = {
        title: '',
        time: 0
      }
      tmpTask.title = this.newTask.title
      tmpTask.time = this.newTask.time
      this.newTask.title = ''
      this.newTask.time = 0
      this.tasks.push(tmpTask)
      localStorage.setItem('tasks', JSON.stringify(this.tasks))
    },
    cancelTask () {
      this.newTask.title = ''
      this.newTask.time = 0
    },
    deleteTask ($event, index) {
      this.tasks.splice(index, 1)
    }
  }

}

</script>

<style lang="scss">
  @import './scss/main.scss';
  .results {
    margin-top: 50px
  }
</style>

Dejo el mío. Me falta implementar algunas cosas pero funciona
https://codepen.io/hiteple/pen/XweZvO

Hecho con bulma en codepen!

https://codepen.io/davos_/pen/arryLP

Ejercicio completado

Adjunto mi ejercicio

<template lang="pug">
  #app
    .panel.is-primary
      .panel-heading Ejercicio de Manipulación del DOM by {{ name}}
      .panel-block
        .columns
          .column
            .box
              .field
                  p.is-size-3 New Task
              .field
                label.control Title:
                input.control(type="text", placeholder="Title", v-model="title")
              .field
                label.control Time
                input.control(type="numeric", placeholder="Time", v-model="time")
              .field
                button.button.is-info(@click="addTask") Add Task
                button.button.is-danger(@click="resetValues") Cancel
          .column
              .fieldset(v-if="tasks.length>0")
                p.is-size-5(v-show="totalTime") Horas trabajadas: {{ totalTime }}
                .columns
                  .column(v-for="(t, key) in tasks")
                    .box
                      p.is-size-7.has-text-weight-bold {{ t.title }}
                      p.is-size-7 {{ t.time}}
                      button.button.is-small(@click="removeTask(key)") Delete
              .fieldset(v-else)
                h1 No tasks yet!
</template>

<script>
export default {
  name: 'app',
  data () {
    return {
      name: 'ANDRES RUIZ',
      tasks: [],
      newTask: {},
      title: '',
      time: 0,
      existsTasks: false
    }
  },
  computed: {
    totalTime () {
      var time = 0
      for (let index = 0; index < this.tasks.length; index++) {
        time += parseInt(this.tasks[index].time)
      }
      return time
    }
  },
  created () {
    this.tasks = JSON.parse(localStorage.getItem('tasks')) || []
  },
  methods: {
    addTask () {
      if ((this.title !== '') && (this.time > 0)) {
        this.tasks.push({
          title: this.title,
          time: this.time
        })
        alert('Added correctly!')
        this.resetValues()
        localStorage.setItem('tasks', JSON.stringify(this.tasks))
      } else {
        alert('Check your values!')
      }
    },
    resetValues () {
      this.title = ''
      this.time = 0
      this.newTask = {}
    },
    removeTask (key) {
      this.tasks.splice(key, 1)
      alert('Deleted correctly!')
      localStorage.setItem('tasks', JSON.stringify(this.tasks))
    }
  }
}
</script>

<style lang="scss">
  @import './scss/main.scss'
</style>

Aporto mi ejercicio, trate de hacer algo diferente pero implementando todos los puntos requeridos:
https://github.com/menkar91/note

Mi Trabajo
JS


export default {
  name: "AddList",

  data() {
    return {
      name: "",
      task: [],
      newTask: {},
      time: 0,
      title: "",
      showMessage: false,
      message: ""
    };
  },
  methods: {
    addTask() {
      if (this.title != "" && this.time > 0) {
        this.task.push({ time: this.time, title: this.title });
        localStorage.setItem("tasks", JSON.stringify(this.tasks));
        this.showMessage = true;
        this.message = "Time was add";
      } else {
        this.showMessage = true;
        this.message = "Time wasn't added";
      }
    },
    restartTask() {
      this.title = "";
      this.time = 0;
      this.rewTask = {};
      this.showMessage = true;
      this.message = "The time is restart";
    },
    remove(index) {
      this.task.splice(index, 1);
      this.showMessage = true;
      this.message = "The time was deleted";
    }
  },
  computed: {
    totalTime() {
      let sumTime = 0;
      for (const key in this.task) {
        sumTime += parseInt(this.task[key].time);
      }
      return sumTime;
    }
  },
  created() {
    this.tasks = JSON.parse(localStorage.getItem("tasks")) || [];
  }
};

Javascript

var app = new Vue({
  el: '#app',
  data: {
    name: 'Lucas Moreno',
    tasks: [],
    newTask: {title:'', time: null},
    showMessage: false
  },
  methods: {
    removeTask(index){
      this.tasks.splice(index, 1);
      localStorage.setItem("tasks", JSON.stringify(this.tasks));
    },
    addTask(){
      if(this.newTask.title != '' && this.newTask.time != null){
        this.tasks.push({title: this.newTask.title, time: this.newTask.time});
        localStorage.setItem("tasks", JSON.stringify(this.tasks));
        this.newTask.title = '';
        this.newTask.time = null;
        this.showMessage = false;
      }
      else{
        this.showMessage = true;
      }
    },
    cancel(){
      this.newTask.title = '';
      this.newTask.time = null;
    }
  },
  computed:{
    totalTime(){
      let suma = 0;
      for(let i = 0; i < this.tasks.length; ++i){
        suma += parseInt(this.tasks[i].time);
      }
      return suma;
    }
  },
  created(){
    this.tasks = JSON.parse(localStorage.getItem("tasks")) || [];
  }
})

pug

#app
  h1 Organizador de tareas :D
  p Nombre: {{ name }}
  section
    input(type="text" v-model="newTask.title" placeholder="Titulo de la tarea")
    input(type="number" v-model="newTask.time" placeholder="Tiempo de la tarea")
    button(@click="addTask") Agregar Tarea
    button(@click="cancel") Cancelar Tarea
  span(v-if="showMessage") Porfavor, no deje ningun espacio vacio
  h2 Tareas por hacer:
  ul
    li(v-for="(task, index) in tasks")
      div
        span Titulo: {{task.title}} <br>
        span Hora: {{task.time}}
      button(@click="removeTask(index)") Tarea realizada
      
  h2 Horas a trabajar: {{ totalTime }}

Hice el ejercicio en codepen, este es el link: https://codepen.io/hectortllo/pen/eYpYxQb?editors=1010

<template lang="pug">
  #pxMoveDom
    h1 {{ name }}
    section.section
      p Ingresa el nombre de la Tarea
      input.input.is-medium( type="text"
      placeholder="Título"
      v-model="newTask.title")
      p Ingresa el tiempo para realizarla
      input.input.is-medium( type="number"
      placeholder="Tiempo"
      v-model="newTask.time")
      a.button.is-info.is-large(@click='addTask') Agregar
      a.button.is-danger.is-large(@click='cancel') &times
      div(v-show="tasks.length")
        ul
          li(v-for="(t,i) in tasks") {{ t.title }} - {{ t.time }}
            a.button.is-danger(@click='removeTask(i)') &times
        h1 Tiempo total trabjado: {{ totalTime }}
      div(v-show="!tasks.length")
        p No hay elementos en la lista
</template>
<script>
export default {
  name: 'pxMoveDOM',
  data() {
    return {
      name: 'Lalo Rivero',
      tasks: [],
      newTask: { title: '', time: null }
    }
  },
  methods: {
    addTask() {
      if (this.newTask.title != '' && this.newTask.time != null) {
        let newObj = JSON.parse(JSON.stringify(this.newTask))
        this.tasks.push(newObj)
        localStorage.setItem('tasks', JSON.stringify(this.tasks))
        this.newTask.title = ''
        this.newTask.time = null
      }
      console.log(this.tasks)
    },
    cancel() {
      this.newTask.title = ''
      this.newTask.time = null
    },
    removeTask(index) {
      this.tasks.splice(index, 1)
      localStorage.setItem('tasks', JSON.stringify(this.tasks))
    }
  },
  computed: {
    totalTime() {
      let time = null
      this.tasks.map(sum => {
        time += parseInt(sum.time)
      })
      return time
    }
  },
  created() {
    this.tasks = JSON.parse(localStorage.getItem('tasks')) || []
  }
}
</script>

Ejercicio realizado:

<template lang="pug">
  #app
    .container
      .control
        input.input(
          type="text"
          placeholder="Titulo",
          v-model="newTask.title"
        )
      .control
        input.input(
          type="text"
          placeholder="Tiempo",
          v-model="newTask.time"
        )
      .control
        button.button.is-info(@click="addTask") Agregar
      .control
        button.button.is-danger(@click="clearNewTask") Cancelar
    .container
      div Nombre: {{ name }}
      div Tiempo total trabajado: {{ totalTime }}
      table.table
        thead
          tr
            th Titulo
            th Tiempo
        tbody
          tr(v-for="(t, index) in tasks")
            td {{ t.title }}
            td {{ t.time }}
            td
              .control
                button.button.is-info(@click="removeTask(index)") Eliminar
</template>

<script>
export default {
  name: 'app',
  data () {
    return {
      name: "",
      tasks: [],
      newTask: {
        title: "",
        time: ""
      }
    }
  },
  methods: {
    addTask () {
      console.debug("Agregando tarea...");
      try {
        const titleNewTask = this.newTask.title;
        const timeNewTask = this.newTask.time;
        if (!(titleNewTask && timeNewTask)) {
          throw new Error("Campos titulo y tiempo son obligatorios!");
        }
        this.tasks.push({
          title: titleNewTask,
          time: parseInt(timeNewTask)
        });
        localStorage.setItem("tasks", JSON.stringify(this.tasks));
        this.clearNewTask();
        console.debug("Tarea agregada!");
      } catch (err) {
        console.error(`Error agregando tarea ${JSON.stringify(this.newTask)}`, err.stack);
      }
    },
    clearNewTask (){
      this.newTask = {
        title: "",
        time: ""
      };
    },
    removeTask (taskIndex) {
      this.tasks.splice(taskIndex, 1);
      localStorage.setItem("tasks", JSON.stringify(this.tasks));
    }
  },
  computed: {
    totalTime () {
      let totalTime = 0;

      this.tasks.forEach(t => {
        totalTime += t.time;
      });

      return totalTime;
    }
  },
  created () {
    this.tasks = JSON.parse(localStorage.getItem("tasks")) || [];
  }
}
</script>

<style lang="scss">
@import './scss/main.scss'
</style>

Solucion del problema para el año 2020

<template>
  <div>
    <section class="section">
      <nav class="navbar">
        <div class="field has-addons">
          <div class="control"><input class="input" type="text" placeholder="Find songs" v-model="new_task.title" /></div>
          <div class="control"><input class="input" type="number" placeholder="Find songs" v-model="new_task.time" /></div>
          <div class="control"><button class="button is-info" @click="addTask">Find</button></div>
          <div class="control"><button class="button is-danger" @click="cancel">&times;</button></div>
          <div class="control">
            <button class="button">
              <span class="is-size-7">Total hours worked: {{ totalTime }}</span>
            </button>
          </div>
        </div>
      </nav>
      <div class="container custom">
        <ul v-if="tasks.length !== 0">
          <li v-for="(task, index) in tasks" :key="task.id">
            <p>{{ task.title }} - {{ task.time }}</p>
            <div class="control"><button class="button is-danger" @click="deleteTask(index)">&times;</button></div>
          </li>
        </ul>
        <p v-else>La lista esta vacia</p>
      </div>
    </section>
  </div>
</template>

<script>
export default {
  name: "Test2",
  data() {
    return {
      name: "",
      tasks: [
        { title: "Job", time: 8 },
        { title: "Eat", time: 2 }
      ],
      new_task: { title: "", time: null }
    };
  },
  created() {
    this.tasks = JSON.parse(localStorage.getItem("tasks")) || [];
  },
  methods: {
    addTask() {
      if (this.new_task.title && this.new_task.time) {
        this.tasks.unshift(this.new_task);
        this.new_task = { title: "", time: null };
        localStorage.setItem("tasks", JSON.stringify(this.tasks));
      }
    },
    deleteTask(index) {
      this.tasks.forEach((element, i) => {
        if (index == i) {
          this.tasks.splice(i, 1);
          localStorage.setItem("tasks", JSON.stringify(this.tasks));
        }
      });
    },
    cancel() {
      this.new_task = { title: "", time: null };
    }
  },
  computed: {
    totalTime() {
      let summation = 0;
      this.tasks.forEach(element => {
        summation += parseInt(element.time, 10);
      });
      return summation;
    }
  }
};
</script>

<style lang="scss">
@import "../scss/main.scss";
.input {
  border: 1px solid black;
}
</style>

Mi version con uso de _lodash

<template lang="pug">
  #app
    h2 {{ name }}

    input(type="text" placeholder="title" v-model="newTask.title" required)
    input(type="number" placeholder="time in hours" v-model="newTask.time" required)
    button(v-on:click="addTask()" class="button is-primary") Generar
    button(v-on:click="cancel()" class="button is-danger") X

    p(v-show="tasks.length === 0 ? true : false") No se han agregado tareas

    ul
      li(v-for="(t, index) in tasks" :key="index")
        p {{ index }} {{ t.title }} - {{ t.time }}
        button.button.is-danger(v-on:click="removeTask(index)") x

    h5(v-show="tasks.length !== 0 ? true : false") Tiempo {{ totalTime }} horas

</template>

<script>
export default {
  name: "App",
  data() {
    return {
      name: "Lista de Tareas",
      tasks: [],
      newTask: {
        title: "",
        time: ""
      }
    };
  },
  created() {
    this.tasks = JSON.parse(localStorage.getItem("tasks")) || [];
  },
  computed: {
    totalTime() {
      let tiempos = this._.map(this.tasks, "time");
      let tiemposNum = this._.map(tiempos, this._.parseInt);
      let totalTime = this._.sum(tiemposNum);
      return totalTime;
    }
  },
  methods: {
    addTask() {
      this.tasks.push({ title: this.newTask.title, time: this.newTask.time });
      localStorage.setItem("tasks", JSON.stringify(this.tasks));
      this.newTask.title = "";
      this.newTask.time = "";
    },
    cancel() {
      this.newTask.title = "";
      this.newTask.time = "";
    },
    removeTask(ind) {
      this.tasks.splice(ind, 1);
      console.log(this.tasks);
    }
  }
};
</script>

<style lang="scss">
@import "@/assets/scss/main.scss";
</style>

<template>
<div id=“app” class=“row mt-5”>
<div class=“col-12”>
<h1>Gestor de Tareas</h1>
<p class=“h6”>Total de horas trabajadas {{totalTime}}</p>
</div>
<div class=“col-4”>
<input class=“form-control input-lg” type="text"
placeholder=“Nueva tarea” v-model=“newTask.title”>
</div>
<div class=“col-4”>
<input v-model=“newTask.time” class=“form-control” type=“number” placeholder=“0”>
</div>
<div class=“col-4”>
<a class=“btn btn-info btn-lg mr-2” @click=“addTask”> Agregar </a>
<a class=“btn btn-danger btn-lg” @click=“cancel” > Cancelar </a>
</div>

    <table v-if="tasks.length > 0" class="table">
      <thead>
      <tr>
        <th scope="col">Tarea</th>
        <th scope="col">Horas</th>
        <th scope="col">Eliminar</th>
      </tr>
      </thead>
      <tbody>
      <tr v-for="(task, index) in tasks" :key="index" >
        <td>{{task.title}}</td>
        <td>{{task.time}}</td>
        <td><a class="btn btn-danger" v-on:click="removetask(index)">Eliminar</a></td>
      </tr>
      </tbody>
    </table>
    <h1 class="h1" v-else>No hay tareas</h1>
</div>

</template>

<script>
export default {
name: ‘App’,
data () {
return {
tasks: [],
newTask: {
title: ‘’,
time: 0
}
}
},
computed: {

totalTime () {
  let total = 0
  this.tasks.forEach(elem => {
    total += parseInt(elem.time)
  })
  return total
}

},
created () {
this.tasks = JSON.parse(localStorage.getItem(‘tasks’)) || []
},
methods: {
addTask () {
if (this.newTask.title !== ‘’ && this.newTask.time > 0) {
this.tasks.push({ title: this.newTask.title, time: this.newTask.time })
localStorage.setItem(‘tasks’, JSON.stringify(this.tasks))
this.cancel ()
} else {
console.log(‘valor no aceptado’)
}
},
cancel () {
this.newTask.title = ''
this.newTask.time = 0
},
removetask (index) {
this.tasks.splice(index, 1)
localStorage.setItem(‘tasks’, JSON.stringify(this.tasks))
}

}

}
</script>

<style lang=“scss”>
@import “./scss/main.scss”;
</style>

Lo hice con bootstrap

<template>
    <div id="app" class="row mt-6 mx-6">

        <div class="container col-6">
          <div class="input-group input-group-lg">
            <input class="form-control input-lg" type="text"
                   placeholder="Nueva tarea"  v-model="newtask.title">
          </div>
          <p class="h6">Total de horas {{totalhoras}}</p>
        </div>

          <select  class="col-2 form-select form-select-lg mb-3" aria-label=".form-select-lg example" v-model="newtask.time">
            <option value = "0" selected>Horas</option>
            <option value="1">1</option>
            <option value="2">2</option>
            <option value="3">3</option>
            <option value="4">4</option>
            <option value="5">5</option>
            <option value="6">6</option>
            <option value="7">7</option>
            <option value="8">8</option>
            <option value="9">9</option>
            <option value="10">10</option>
            <option value="11">11</option>
            <option value="12">12</option>
          </select>

        <div class="col-4">
          <a class="btn btn-info btn-lg mr-2" v-on:click="agregar"> Agregar </a>
          <a class="btn btn-danger btn-lg" v-on:click="borrar" > Borrar lista </a>
        </div>

        <div class="coninater col-12 mt-3">
          <ul>
<!--            <li v-for="t in tracks" v-bind:key="t">{{t.name}} - {{ t.artist}}</li>-->
          </ul>
        </div>

        <table v-if="tasks.length > 0" class="table">
          <thead>
          <tr>
            <th scope="col">Tarea</th>
            <th scope="col">Horas</th>
            <th scope="col">Eliminar</th>
          </tr>
          </thead>
          <tbody>
          <tr v-for="task in tasks" v-bind:key="task.title">
            <td>{{task.title}}</td>
            <td>{{task.time}}</td>
            <td><a class="btn btn-danger" v-on:click="removetask(task)">Eliminar</a></td>
          </tr>

          </tbody>
        </table>
        <h1 class="h1" v-else>No hay tareas</h1>

    </div>

</template>

<script>
// const reducer = (accumulator, currentValue) => accumulator + currentValue

export default {
  name: 'App',
  data () {
    return {
      name: '',
      time: '',
      tasks: [],
      delete: 0,

      newtask: {
        title: '',
        time: 0
      }

    }
  },
  computed: {

    totalhoras () {
      // this.newtask = this.tasks.time.slice()
      let horas = 0
      for (const task of this.tasks) {
        horas += parseInt(task.time)
      }
      return horas
    }
  },
  created () {
    this.tasks = JSON.parse(localStorage.getItem('tasks')) || []
  },
  methods: {

    agregar () {
      if (!(this.tasks.find(tarea => tarea.title === this.newtask.title)) && this.newtask.time > 0) {
        this.tasks.push({ title: `${this.newtask.title}`, time: `${this.newtask.time}` })
        localStorage.setItem('tasks', JSON.stringify(this.tasks))
        this.newtask = []
      } else {
        console.log('valor no aceptado')
      }
    },
    borrar () {
      this.tasks = []
    },
    removetask (titulo) {
      this.delete = this.tasks.indexOf(titulo)
      // console.log(this.tasks.indexOf(titulo))
      this.tasks.splice(this.delete, 1)
      localStorage.setItem('tasks', JSON.stringify(this.tasks))
    }

  }

}
</script>

<style lang="scss">
@import "./scss/main.scss";
</style>

Hola a todos!!

Creo que el diseño no esta chevere pero me gusto construir la funcionalidad…
Agregue un elemento de validación al input que se muestra si se intenta guardar con errores

😉

Codepen- Manipulación del DOM