In order to efficiently gather data for multiple projects over time and keep track of each day within them, I have devised the following data structure:
day = ''
week = [day, day, day]
project = [week, week, ...]
For simplicity's sake, it only allows for adding future weeks. While it can be modified to accommodate more flexibility, doing so would increase complexity in the models without necessarily enhancing understanding of how to link data to the model.
Each week should have a model that stores daily data using an array of empty strings:
week: ['','','','','','','']
Multiple weeks can be included in each project:
data: [week, week, week]
When a user creates a new project, it should replicate the current project model based on the active week:
_.cloneDeep(project(this.weekNum, this.rows.length))
With the data structure set up, it is time to link the view to it:
<input type="text" style="width: 35px;" v-model="row.data[weekNum][i]">
Please refer to the snippet below to understand how everything connects together:
const weekData = () => new Array(7).fill('');
const project = (weekNum, id) => ({
project: "first",
id,
data: Array(weekNum + 1).fill([]).map(weekData)
});
new Vue({
el: "#app",
data: {
weekNum: 0,
rows: [_.cloneDeep(project(0, 0))]
},
methods: {
addProject() {
window.pp = _.cloneDeep(
project(this.weekNum, this.rows.length)
)
this.rows.push(
window.pp
);
},
deleteRow(key) {
this.rows.splice(key, 1);
},
nextWeek() {
this.weekNum++;
this.rows.forEach(_project => {
if (!_project.data[this.weekNum]) {
_project.data.push(weekData());
}
});
},
prevWeek() {
this.weekNum--;
this.rows.forEach(row => {
if (!row.data[this.weekNum]) {
row.data.unshift(weekData());
}
});
},
dates(dateFormat, weekNumber) {
let startOfWeek = moment().startOf('week').add(weekNumber, "week");
const endOfWeek = moment().endOf('week').add(weekNumber, "week");
const days = [];
while (startOfWeek <= endOfWeek) {
days.push(startOfWeek.format(dateFormat))
startOfWeek = startOfWeek.clone().add(1, 'd');
}
return days
},
log() {
const output = _.reduce(this.rows, (result, row) => {
const project = {
project: row.id
};
const startWeek = moment().startOf('week');
const weekDays = [];
row.data.forEach((week, weekIdx) => {
week.forEach((data, dataIdx) => {
if (data === '') return;
weekDays.push({
data,
project: row.id,
day: startWeek.clone().add(weekIdx, 'week').add(dataIdx, 'd').format('MMM D')
});
});
});
return [...result, ...weekDays];
}, []);
console.log(output)
}
}
})
<script src="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="0a6765676f647e4a3824383e243a">[email protected]</a>/moment.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="b1ddded5d0c2d9f1859f80869f8080">[email protected]</a>/lodash.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="80f6f5e5c0b2aeb5aeb2b2">[email protected]</a>/dist/vue.min.js"></script>
<div id="app">
<!-- for the sake of simplicity limit the date to future weeks -->
<button @click="prevWeek" :disabled="weekNum < 1">Previous week</button>
<button @click="nextWeek">Next week</button>
<button @click="addProject">Add project</button>
<table>
<tr>
<th>Project</th>
<th v-for="(day, i) in dates('MMM D', weekNum)" :key="i">{{day}}</th>
</tr>
<tbody>
<tr v-for="(row, key) in rows" :key="key">
<td>Project {{key}}</td>
<td v-for="(n,i) in dates('YYYY-MM-DD', weekNum)" :key="`${row.id}-${i}`">
<input type="text" style="width: 35px;" v-model="row.data[weekNum][i]">
</td>
<td><button @click="deleteRow(key)">x</button></td>
</tr>
</tbody>
</table>
<button @click="log()">log</button>
</div>