mirror of
https://github.com/DCsunset/taskwarrior-webui.git
synced 2025-08-24 05:56:42 +02:00
feat(frontend): add basic UI
This commit is contained in:
parent
4cf86cdc72
commit
e4b5a58518
3 changed files with 292 additions and 10 deletions
61
frontend/components/TaskDialog.vue
Normal file
61
frontend/components/TaskDialog.vue
Normal file
|
@ -0,0 +1,61 @@
|
|||
<template>
|
||||
<v-dialog v-model="showDialog" max-width="600px" persistent>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
{{ task ? 'Edit Task' : 'New Task' }}
|
||||
</v-card-title>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn text @click="cloesDialog">
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-btn color="primary" @click="submit">
|
||||
Submit
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, reactive, ref } from '@vue/composition-api';
|
||||
import { Task } from 'taskwarrior-lib';
|
||||
|
||||
interface Props {
|
||||
[key: string]: unknown,
|
||||
value: boolean,
|
||||
task?: Task;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
task: {
|
||||
type: Object,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
setup(props: Props, context) {
|
||||
const showDialog = computed({
|
||||
get: () => props.value,
|
||||
set: val => context.emit('input', val)
|
||||
});
|
||||
const cloesDialog = () => {
|
||||
showDialog.value = false;
|
||||
};
|
||||
const submit = () => {
|
||||
// TODO: submit
|
||||
cloesDialog();
|
||||
};
|
||||
|
||||
return {
|
||||
cloesDialog,
|
||||
submit,
|
||||
showDialog
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
194
frontend/components/TaskList.vue
Normal file
194
frontend/components/TaskList.vue
Normal file
|
@ -0,0 +1,194 @@
|
|||
<template>
|
||||
<div>
|
||||
<TaskDialog v-model="showTaskDialog" :task="currentTask" />
|
||||
|
||||
<v-btn-toggle v-model="status" mandatory>
|
||||
<v-row class="pa-3">
|
||||
<v-btn
|
||||
v-for="st in allStatus"
|
||||
:key="st"
|
||||
:value="st"
|
||||
:color="st === status ? 'primary' : undefined"
|
||||
text
|
||||
@click="st !== status && (selected = [])"
|
||||
>
|
||||
<v-icon
|
||||
class="mr-1"
|
||||
:color="st === status ? 'primary' : undefined"
|
||||
>
|
||||
{{ statusIcons[st] }}
|
||||
</v-icon>
|
||||
{{ st }}
|
||||
<v-badge
|
||||
v-if="st === 'pending'"
|
||||
:content="classifiedTasks[st].length"
|
||||
:color="st === status ? 'primary' : 'grey darken-1'"
|
||||
inline
|
||||
/>
|
||||
</v-btn>
|
||||
</v-row>
|
||||
</v-btn-toggle>
|
||||
|
||||
<v-data-table
|
||||
:items="classifiedTasks[status]"
|
||||
:headers="headers"
|
||||
show-select
|
||||
v-model="selected"
|
||||
class="elevation-1"
|
||||
>
|
||||
<template v-slot:top>
|
||||
<v-row class="px-4">
|
||||
<!-- Batch actions -->
|
||||
<div class="pl-2 pt-2" v-show="selected.length">
|
||||
<v-btn
|
||||
v-show="status === 'pending'"
|
||||
class="ma-1 green"
|
||||
fab
|
||||
small
|
||||
dark
|
||||
title="Done"
|
||||
>
|
||||
<v-icon>mdi-check</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-show="status !== 'deleted'"
|
||||
class="ma-1 red"
|
||||
fab
|
||||
dark
|
||||
small
|
||||
title="Delete"
|
||||
@click="deleteTasks(selected)"
|
||||
>
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<v-spacer />
|
||||
|
||||
<!-- Global Actions -->
|
||||
<div class="ma-2">
|
||||
<v-btn
|
||||
class="green ml-1"
|
||||
fab
|
||||
dark
|
||||
title="Refresh"
|
||||
@click="refresh"
|
||||
>
|
||||
<v-icon>mdi-refresh</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
class="primary ml-1"
|
||||
fab
|
||||
dark
|
||||
title="New task"
|
||||
@click="newTask"
|
||||
>
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<template v-slot:item.actions="{ item }">
|
||||
<v-icon
|
||||
small
|
||||
class="mr-2"
|
||||
@click="editTask(item)"
|
||||
>
|
||||
mdi-pencil
|
||||
</v-icon>
|
||||
<v-icon
|
||||
small
|
||||
@click="deleteTasks([item])"
|
||||
>
|
||||
mdi-delete
|
||||
</v-icon>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, reactive, ref, ComputedRef, Ref } from '@vue/composition-api';
|
||||
import { Task } from 'taskwarrior-lib';
|
||||
import _ from 'lodash';
|
||||
import TaskDialog from '../components/TaskDialog.vue';
|
||||
|
||||
interface Props {
|
||||
[key: string]: unknown,
|
||||
tasks: Task[]
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
tasks: {
|
||||
type: Array as () => Task[]
|
||||
}
|
||||
},
|
||||
|
||||
setup(props: Props, context) {
|
||||
const headers = [
|
||||
{ text: 'Project', value: 'project' },
|
||||
{ text: 'Description', value: 'description' },
|
||||
{ text: 'Priority', value: 'priority' },
|
||||
{ text: 'Due', value: 'due' },
|
||||
{ text: 'Tags', value: 'tags' },
|
||||
{ text: 'Actions', value: 'actions', sortable: false }
|
||||
];
|
||||
|
||||
const selected = ref([] as Task[]);
|
||||
|
||||
const status = ref('pending');
|
||||
const allStatus = ['pending', 'waiting', 'completed', 'deleted', 'recurring'];
|
||||
const statusIcons = {
|
||||
pending: 'mdi-clock-outline',
|
||||
waiting: 'mdi-pause',
|
||||
completed: 'mdi-check',
|
||||
deleted: 'mdi-delete',
|
||||
recurring: 'mdi-restart'
|
||||
};
|
||||
|
||||
const tempTasks: { [key: string]: ComputedRef<Task[]> } = {};
|
||||
for (const status of allStatus) {
|
||||
tempTasks[status] = computed((): Task[] => props.tasks?.filter(task => task.status === status));
|
||||
}
|
||||
const classifiedTasks = reactive(tempTasks);
|
||||
|
||||
const refresh = () => {
|
||||
context.root.$store.dispatch('fetchTasks');
|
||||
};
|
||||
|
||||
const showTaskDialog = ref(false);
|
||||
const currentTask: Ref<Task> = ref(null);
|
||||
const newTask = () => {
|
||||
showTaskDialog.value = true;
|
||||
currentTask.value = null;
|
||||
};
|
||||
|
||||
const editTask = (_task: Task) => {
|
||||
// TODO
|
||||
};
|
||||
|
||||
const deleteTasks = async (tasks: Task[]) => {
|
||||
await context.root.$store.dispatch('deleteTasks', tasks);
|
||||
_.remove(selected.value, task => tasks.findIndex(t => t.uuid === task.uuid) !== -1);
|
||||
};
|
||||
|
||||
return {
|
||||
refresh,
|
||||
headers,
|
||||
classifiedTasks,
|
||||
status,
|
||||
allStatus,
|
||||
statusIcons,
|
||||
selected,
|
||||
newTask,
|
||||
currentTask,
|
||||
editTask,
|
||||
deleteTasks,
|
||||
showTaskDialog,
|
||||
TaskDialog
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
|
@ -1,14 +1,41 @@
|
|||
<template>
|
||||
<v-layout
|
||||
column
|
||||
justify-center
|
||||
align-center
|
||||
>
|
||||
Hello
|
||||
</v-layout>
|
||||
<div class="px-md-6 px-lg-12">
|
||||
<v-row class="px-4 pt-4">
|
||||
<div class="headline d-flex align-center">{{ mode }}</div>
|
||||
<v-spacer />
|
||||
<v-select
|
||||
class="mb-2"
|
||||
:items="allModes"
|
||||
label="Display Mode"
|
||||
v-model="mode"
|
||||
style="max-width: 120px"
|
||||
hide-details
|
||||
/>
|
||||
</v-row>
|
||||
|
||||
<TaskList :tasks="tasks" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
};
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed } from '@vue/composition-api';
|
||||
import TaskList from '../components/TaskList.vue';
|
||||
|
||||
export default defineComponent({
|
||||
setup(_props, context) {
|
||||
context.root.$store.dispatch('fetchTasks');
|
||||
|
||||
const mode = ref('Tasks');
|
||||
const allModes = ['Tasks', 'Projects'];
|
||||
|
||||
const tasks = computed(() => context.root.$store.state.tasks);
|
||||
|
||||
return {
|
||||
mode,
|
||||
allModes,
|
||||
TaskList,
|
||||
tasks
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue