<template>
<div class="chore-details" v-if="editChore">
<AppSidebar
:title.sync="editChore.name"
:title-editable="true"
@close="closeSideBar()"
>
<div class="sidebar-details-content">
<div class="sidebar-entry">
<div class="sidebar-entry-label">
<label for="chorePoints">
Effort
</label>
</div>
<div class="sidebar-entry-field">
<select v-model="editChore.points" name="chorePoints">
<option value="1">1 point</option>
<option value="2">2 points</option>
<option value="3">3 points</option>
<option value="4">4 points</option>
<option value="5">5 points</option>
<option value="6">6 points</option>
</select>
</div>
</div>
<div class="sidebar-entry">
<div class="sidebar-entry-label">
Due
</div>
<div class="sidebar-entry-field">
<DatetimePicker v-model="due" />
</div>
<span v-if="debug" class="muted">{{editChore.due}}</span>
</div>
<div class="sidebar-entry">
<div class="sidebar-entry-label">
Recurrence
<span v-if="debug" class="muted"> ({{repeatScheduleText}})</span>
</div>
<div class="sidebar-entry-field">
<input v-model="editChore['isRepeating']" type="checkbox"
name="choreIsRepeating">
<label for="choreIsRepeating"
@click="editChore.isRepeating = !editChore.isRepeating">
Repeat this task indefinitely
</label>
</div>
<div class="sidebar-entry" v-if="editChore['isRepeating']">
<div class="sidebar-entry-label">
Schedule
</div>
<div class="sidebar-entry-field">
<select v-model="editChore.schedule">
<option value="o:1">on-demand</option>
<optgroup label="daily">
<option value="d:1">every day</option>
<option value="d:2">every 2 days</option>
<option value="d:3">every 3 days</option>
<option value="d:4">every 4 days</option>
<option value="d:5">every 5 days</option>
<option value="d:6">every 6 days</option>
</optgroup>
<optgroup label="weekly">
<option value="w:1">every week</option>
<option value="w:2">every 2 weeks</option>
<option value="w:3">every 3 weeks</option>
<option value="w:4">every 4 weeks</option>
</optgroup>
<optgroup label="monthly">
<option value="m:1">every month</option>
<option value="m:2">every 2 months</option>
<option value="m:3">quarterly</option>
<option value="m:4">every 4 months</option>
<option value="m:6">biannual</option>
</optgroup>
<optgroup label="yearly">
<option value="m:12">annual</option>
</optgroup>
</select>
</div>
<div class="sidebar-entry-field" v-if="canConstrain">
<input v-model="editChore['isConstrained']" type="checkbox"
name="choreIsConstrained">
<label for="choreIsConstrained"
@click="editChore.isConstrained = !editChore.isConstrained">
{{constraintText}}
</label>
</div>
</div>
</div>
<div>
<button class="primary" :disabled="!modified" @click="save()">
Save changes
</button>
</div>
</div>
<div v-if="debug" class="muted">
edit: {{ JSON.stringify(editChore, null, 2) }}
</div>
<div v-if="debug" class="muted">
{{ JSON.stringify(chore, null, 2) }}
</div>
</AppSidebar>
</div>
</template>
<script>
import { AppSidebar, DatetimePicker } from '@nextcloud/vue';
const WEEKDAY_NAMES = [
'Sunday',
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
];
function dateFormatISO8601(date) {
const offset = -date.getTimezoneOffset();
const absOffset = Math.abs(offset);
const offsetSign = offset >= 0 ? '+' : '-';
const pad = function(num) {
const rounded = Math.floor(num);
return (rounded < 10 ? '0' : '') + rounded;
};
return `${date.getFullYear()}-${pad(date.getMonth()+1)}-${pad(date.getDate())}`
+ 'T'
+ `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
+ `${offsetSign}${pad(absOffset / 60)}:${pad(absOffset % 60)}`;
}
export default {
name: 'ChoreDetailsSidebar',
props: {
choreId: {
type: [String, Number],
required: true,
},
mode: {
type: String,
required: true,
},
},
components: {
AppSidebar,
DatetimePicker,
},
watch: {
chore() {
this.copyData();
},
},
data() {
return {
debug: false,
store: this.$root.$data.globalStore,
editChore: {
id: null,
name: '',
isRepeating: false,
schedule: "s:0:-",
due: new Date(),
isConstrained: false,
points: 1,
},
}
},
mounted() {
this.copyData();
},
methods: {
copyData() {
if (this.editChore && this.chore && this.editChore.id === this.chore.id) {
return
}
const clone = JSON.parse(JSON.stringify(this.chore));
clone.isRepeating = !this.chore.repeat.match(/^s:/);
clone.isConstrained = !this.chore.repeat.match(/:-$/);
if (clone.isRepeating) {
clone.schedule = this.chore.repeat.split(":").slice(0,2).join(":");
} else {
clone.schedule = 'd:1';
}
Object.assign(this.editChore, clone);
},
async save() {
const updatedChore = {
id: this.editChore.id,
name: this.editChore.name,
due: this.editChore.due,
points: this.editChore.points,
repeat: this.repeatScheduleText,
};
const resultProm = this.store.saveChoresData(updatedChore);
if (this.editChore.id === -1) {
const result = await resultProm;
this.$router.replace(`/${this.$props.mode}/chore/${result.id}`);
}
},
closeSideBar() {
this.$router.replace(`/${this.$props.mode}`);
},
},
computed: {
chore: function() {
if (this.$props.choreId === 'new') {
return {
id: -1,
assignee: '',
name: 'New Chore Name',
due: new Date(),
points: 1,
repeat: 's:1:-',
};
}
const chore = this.store.choreById(Number(this.$props.choreId));
return chore || {repeat:'s:1:-', due: new Date(), id:-1};
},
due: {
get() {
return new Date(this.editChore.due);
},
set(value) {
const clone = new Date(value);
clone.setHours(12);
clone.setMinutes(0);
clone.setSeconds(0);
this.editChore.due = dateFormatISO8601(clone);
},
},
repeatScheduleText() {
if (this.editChore.isRepeating === false) {
return 's:1:-';
}
const constraint = this.editChore.isConstrained ? this.constraintInt : '-';
return this.editChore.schedule + ':' + constraint;
},
modified: function() {
return (this.chore.name !== this.editChore.name)
|| (this.chore.due !== this.editChore.due)
|| (this.chore.points !== this.editChore.points)
|| (this.chore.repeat !== this.repeatScheduleText)
;
},
canConstrain: function() {
const unit = this.editChore.schedule.split(':')[0];
return unit === 'w' || unit === 'm';
},
constraintText: function() {
const unit = this.editChore.schedule.split(':')[0];
if (unit === 'w') {
const weekday = this.due.getDay() % 7;
const weekdayName = WEEKDAY_NAMES[weekday];
return `Always on a ${weekdayName}`;
}
if (unit === 'm') {
const dayOfMonth = this.due.getDate();
return `Always on the ${dayOfMonth}. of the month`;
}
return '-';
},
constraintInt: function() {
const unit = this.editChore.schedule.split(':')[0];
if (unit === 'w') {
return this.due.getDay() % 7;
}
if (unit === 'm') {
return this.due.getDate();
}
return '-';
},
},
}
</script>
<style>
.sidebar-entry {
margin: 0.5rem 0;
}
.sidebar-entry-label {
font-weight: bold;
}
.sidebar-entry-field {
}
.sidebar-details-content {
margin: 10px;
}
.muted {
color: #f0f;
}
input[type="checkbox"] {
min-height: 0;
}
</style>