MomasVII
MomasVII

Reputation: 5091

Calculating multiple totals from a v-for loop

I have a unique situation where I have a v-for loop of users (Staff) and inside that I have another v-for loop checking the leave a user has accumulated.

so to put it simply

v-for get user
    //Print users name
    v-for get any leave associated with this user
          //Print that days Annual Leave
          //Print that days Sick Leave
    v-end
    //Print total Annual Leave
    //Print total Sick Leave
v-end

The leave database content has these values

Type: (Sick Leave, Annual Leave, Bereavement, etc) Hours: integer

So essentially it will say

Thomas               Annual        Sick
------------------------------------------
Annual Leave         2 hours       0 Hours
Sick Leave           0 Hours       3 Hours
Annual Leave         4 Hours       0 Hours
-------------------------------------------
Total                6 Hours       3 Hours

John                 Annual        Sick
------------------------------------------
Annual Leave         2 hours       0 Hours
Annual Leave         2 Hours       0 Hours
-------------------------------------------
Total                4 Hours       0 Hours

Now for the code and what I have so far:

<div v-for="user_list in user_list_filtered()">
   <div class="user_heading"><h2>{{ user_list.first_name }}</h2></div>
   <div class="report_content" v-for="userleave in user_leave_filtered(user_list['.key'])">
      <div class="leave_type_content">
         {{ userleave.type }}
      </div>
      <div class="days_taken_content">
         //Get Leave
      </div>
      <div class="lsl_content">
         //Get Sick Leave
      </div>
   </div>
   <div class="leave_content">
      <div class="total_leave_title">
         Total Leave Taken
      </div>
      <div class="total_hours">
         // Print Total Leave
      </div>
      <div class="total_hours">
         //Print Total Sick Leave
      </div>
   </div>
</div>

So if it is of Type Sick Leave add it to the second column and set the first column to 0 or if !== Sick Leave set first column to value and second column to 0. Then add each side up and print below.

I have tried some things as functions but I get infinite loops so I am kinda stuck as most posts are not as complicated as what I am trying to achieve.

Thanks for the help

Edit: Additional functions

user_leave_filtered(userPassed) {
  var self = this
  return this.userLeave.filter(function (i) {
    if (i.users_id === userPassed &&
      ((i.start_time >= self.getUnix(self.firstDate) && i.start_time <= self.getUnix(self.lastDate)) ||
        (self.firstDate === null || self.firstDate === '' || self.lastDate === null || self.lastDate === ''))) {
      return true
    } else {
      return false
    }
  })
},
user_list_filtered() {
  var self = this

  return this.userList.filter(function (i) {

    var passed = false

    if (self.userToShow === i['.key'] || self.userToShow === 'All') {
      // Track whether to filter out this leave or not
      self.userLeave.forEach(function (element) {
        if (element.users_id === i['.key']) {
          passed = true
        } else {}
      })
    }

    return passed
  })
},

Upvotes: 1

Views: 150

Answers (2)

Frondor
Frondor

Reputation: 3466

First rule of thumb, don't call functions in your HTML. Use computed properties instead.

You can get a filtered user list and map it to present the data you need per user.

Anyway, I recommend you to handle the mapping of "user leaves" in the backend, and bring the data as close as you'll use it in the client.

This is an example of how I'd address your case (notice I don't use the same object structure you are probably using, since you didn't provide the full code in your question)

new Vue({
  el: "#app",
  data: {
    userList: [
      { id: 1, firstName: "Jon Doe" },
      { id: 2, firstName: "Jane Doe" }
    ],
    userLeave: [
      { userId: 1, type: "anual", hours: 2 },
      { userId: 1, type: "sick", hours: 3 },
      { userId: 1, type: "anual", hours: 4 },
      { userId: 2, type: "anual", hours: 2 },
      { userId: 2, type: "sick", hours: 3 },
      { userId: 2, type: "anual", hours: 4 }
    ]
  },
  computed: {
    usersById () {
      if (!this.userList.length) return null;
      // create a list of users by id and save a few iterations
      return this.userList.reduce((list, user) => {
        list[user.id] = user;
        return list  
      }, {})
    },
    filteredUsers () {
      if (!this.userLeave.length) return [];
      const users = {}
      this.userLeave.forEach(leave => {
        const user = this.usersById[leave.userId]
        if (user) {
          if (leave.type === "sick") {
            user.totalSick = typeof user.totalSick === "number"
              ? leave.hours + user.totalSick
              : leave.hours;
          } else {
            user.totalAnual = typeof user.totalAnual === "number"
              ? leave.hours + user.totalAnual
              : leave.hours;
          }
          if (user.leaves === undefined) user.leaves = []
          user.leaves.push(leave)
          users[user.id] = user
        }
      })
      return users
    }
  }
})
.leave_type_content,
.days_taken_content,
.lsl_content,
.total_leave_title,
.total_hours,
.total_hours {
  display: inline-block
}
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
<div id="app">
  <div v-for="user in filteredUsers"> <!-- NOTICE THE COMPUTED PROPERTY -->
   <div class="user_heading"><h2>{{ user.firstName }}</h2></div>
   <div class="report_content" v-for="userleave in user.leaves">
      <div class="leave_type_content">
         {{ userleave.type }}
      </div>
      <div class="days_taken_content">
         {{ userleave.type === "anual" && userleave.hours || 0 }} hours
      </div>
      <div class="lsl_content">
         {{ userleave.type === "sick" && userleave.hours || 0 }} hours
      </div>
   </div>
   <div class="leave_content">
      <div class="total_leave_title">
         Total Leave Taken
      </div>
      <div class="total_hours">
         {{ user.totalAnual }}
      </div>
      <div class="total_hours">
         {{ user.totalSick }}
      </div>
   </div>
</div>
</div>

Upvotes: 2

Phil
Phil

Reputation: 165065

I would create a computed property holding the users you want to display along with their mapped leave and totals. For example

computed: {
  usersWithLeave () {
    const unixFirstDate = this.firstDate && this.getUnix(this.firstDate)
    const unixLastDate = this.lastDate && this.getUnix(this.lastDate)
    // first map the leave entries by user for quick access
    const leaveByUser = this.userLeave.reduce((map, element) => {
      // Filter out by dates
      if (
          (!unixFirstDate || element.start_time >= unixFirstDate) &&
          (!unixLastDate || element.start_time <= unixLastDate)
      ) {
        const elements = map.get(element.users_id) || []
        elements.push(element)
        map.set(element.users_id, elements)  
      }
      return map
    }, new Map())

    // now construct a filtered array of users then map to one with leave attached
    return this.userList
        .filter(({'.key': id}) => [id, 'All'].includes(this.userToShow) && leaveByUser.has(id))
        .map(({'.key': id, first_name}) => {
          const leave = leaveByUser.get(id)
          return {
            first_name,
            leave, // an array of all leave elements
            totals: leave.reduce((totals, element) => {
              totals[element.type === 'Sick Leave' ? 'sick' : 'annual'] += element.Hours
              return totals
            }, { sick: 0, annual: 0 })
          }
        })
  }
}

Whoo, that was more work than expected. This will produce an array of objects that look something like

{
  first_name: 'Thomas',
  leave: [
    { type: 'Annual Leave', Hours: 2 },
    { type: 'Sick Leave', Hours: 3 },
    { type: 'Annual Leave', Hours: 4 }
  ],
  totals: {
    sick: 3,
    annual: 6
  }
}

Now you can easily use this in your template

<div v-for="user in usersWithLeave">
  <div class="user_heading"><h2>{{ user.first_name }}</h2></div>
  <div class="report_content" v-for="userleave in user.leave">
      <div class="leave_type_content">
        {{ userleave.type }}
      </div>
      <div class="days_taken_content">
        {{ userleave.type !== 'Sick Leave' && userleave.Hours || 0 }}
      </div>
      <div class="lsl_content">
      {{ userleave.type === 'Sick Leave' && userleave.Hours || 0 }}
      </div>
  </div>
  <div class="leave_content">
      <div class="total_leave_title">
        Total Leave Taken
      </div>
      <div class="total_hours">
        {{ userleave.totals.annual }}
      </div>
      <div class="total_hours">
        {{ userleave.totals.sick }}
      </div>
  </div>
</div>

Upvotes: 1

Related Questions