Default start, dialog modals, and more

This commit is contained in:
NetMan 2024-02-15 00:45:08 +01:00
parent fe864bb51a
commit 7180b51746
10 changed files with 543 additions and 85 deletions

34
package-lock.json generated
View File

@ -8,6 +8,9 @@
"name": "odin-todo-vue",
"version": "0.0.0",
"dependencies": {
"@kouts/vue-modal": "^5.0.0",
"@vuepic/vue-datepicker": "^8.1.1",
"date-fns": "^3.3.1",
"lucide-vue-next": "^0.330.0",
"v-dropdown-menu": "^2.0.4",
"vue": "^3.4.15",
@ -608,6 +611,14 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@kouts/vue-modal": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@kouts/vue-modal/-/vue-modal-5.0.0.tgz",
"integrity": "sha512-03R4FaSp2q/rvXr51+YA9g/zszEeexObFimnyVDniT+PiK2QXbG8wnjEkmwSJSGxXs+ravyaVKFSa+SbLpDgoQ==",
"dependencies": {
"vue": "^3.3.4"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -974,6 +985,20 @@
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz",
"integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw=="
},
"node_modules/@vuepic/vue-datepicker": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@vuepic/vue-datepicker/-/vue-datepicker-8.1.1.tgz",
"integrity": "sha512-/t9+dROb/hYN/DInff8ctIiVm5tVyuJdiWA+nWcjHOLjUhvAQ9vyJwhxAkoWX5o0EB5x5XeCME2cajfTPz86RA==",
"dependencies": {
"date-fns": "^3.3.1"
},
"engines": {
"node": ">=18.12.0"
},
"peerDependencies": {
"vue": ">=3.2.0"
}
},
"node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
@ -1332,6 +1357,15 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"node_modules/date-fns": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz",
"integrity": "sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",

View File

@ -11,6 +11,9 @@
"format": "prettier --write src/"
},
"dependencies": {
"@kouts/vue-modal": "^5.0.0",
"@vuepic/vue-datepicker": "^8.1.1",
"date-fns": "^3.3.1",
"lucide-vue-next": "^0.330.0",
"v-dropdown-menu": "^2.0.4",
"vue": "^3.4.15",

View File

@ -2,30 +2,83 @@
import { X, GripVertical, MoreVertical, Edit3 } from 'lucide-vue-next';
import DropdownMenu from 'v-dropdown-menu';
import { Modal } from '@kouts/vue-modal'
import { ref } from 'vue';
// import 'v-dropdown-menu/dist/vue3/v-dropdown-menu.css'
const props = defineProps(['item', 'remove', 'currentProject', 'rename'])
const showModalRename = ref(false);
const showModalDelete = ref(false);
function removeProject(event) {
event.preventDefault();
// setTimeout(() => showModalDelete.value = false, 1)
// setTimeout(() => props.remove(props.item), 121)
props.remove(props.item);
}
function renameProject(event) {
event.preventDefault();
let form = new FormData(event.srcElement);
props.rename(props.item, form.values().next().value);
setTimeout(() => showModalRename.value = false, 1)
}
</script>
<template>
<div class="flex items-center justify-between gap-2 *:block">
<p class="handle text-xl cursor-pointer rounded-lg *:stroke-slate-400 *:stroke-1 hover:*:stroke-slate-300" title="Drag"><GripVertical /></p>
<p class="!overflow-hidden whitespace-nowrap text-ellipsis text-xl flex-1 cursor-pointer hover:bg-slate-600 rounded-lg p-1" :title="props.item.name" @click="$emit('changeCurrentProject', props.item)">{{ props.item.name }}</p>
<p class="handle text-xl cursor-pointer rounded-lg *:stroke-slate-400 *:stroke-1 hover:*:stroke-slate-300 active:cursor-move" title="Drag"><GripVertical /></p>
<p class="!overflow-hidden whitespace-nowrap text-ellipsis text-xl flex-1 cursor-pointer hover:bg-slate-600 rounded-lg p-1" :title="props.item.name" @click="$emit('changeCurrentProject', props.item)" :class="props.currentProject == props.item ? 'bg-slate-500/50 hover:bg-slate-500/75' : ''">{{ props.item.name }}</p>
<!-- <button class="cursor-pointer hover:bg-slate-600 rounded-lg p-1" type="button" @click="props.remove(item)"><X /></button> -->
<dropdown-menu class="relative rounded-full hover:bg-slate-500" title="More">
<dropdown-menu class="relative rounded-full hover:bg-slate-500 *:hover:[&_button]:*:stroke-slate-300" title="More">
<template #trigger>
<button class="p-2"><MoreVertical /></button>
<button class="p-2 *:stroke-slate-400 hover:*:stroke-slate-300"><MoreVertical /></button>
</template>
<template #body>
<ul class="*:flex absolute right-0 bg-slate-600 p-2 rounded-lg flex flex-col gap-1 *:cursor-pointer *:p-1 *:rounded border border-slate-500 z-10">
<li class="hover:bg-slate-500" @click="props.rename(item)"><Edit3 /> Rename</li>
<li class="hover:bg-slate-500" @click="props.remove(item)"><X /> Delete</li>
<!-- <li class="hover:bg-slate-500" @click="props.rename(item)"><Edit3 /> Rename</li>
<li class="hover:bg-slate-500" @click="props.remove(item)"><X /> Delete</li> -->
<li class="hover:bg-slate-500" @click="showModalRename = true"><Edit3 /> Rename</li>
<li class="hover:bg-slate-500" @click="showModalDelete = true"><X /> Delete</li>
</ul>
</template>
</dropdown-menu>
<Modal v-model="showModalRename" title="Rename project">
<template v-slot:default>
<form method="post" @submit="renameProject">
<div class="form-group">
<label for="inputEmail4" class="text-xl block">New name</label>
<input id="inputEmail4" type="text" class="block bg-slate-700 text-lg p-1 rounded-lg border border-slate-600" placeholder="New name" required name="name" />
</div>
<div class="row modal-footer">
<div class="*:p-2 *:rounded-lg *:border flex gap-2 mt-3">
<button class="bg-green-800 border-green-600" type="submit">Rename</button>
<button class="bg-red-900 border-red-700" type="button" @click="showModalRename = false">Discard</button>
</div>
</div>
</form>
</template>
</Modal>
<Modal v-model="showModalDelete" title="Delete project">
<template v-slot:default>
<form method="post" @submit="removeProject">
<div class="form-group">
<label for="inputEmail4" class="text-2xl block">Delete {{ props.item.name }}?</label>
</div>
<div class="row modal-footer">
<div class="*:p-2 *:rounded-lg *:border flex gap-2 mt-3">
<button class="bg-red-900 border-red-700" type="submit">Delete</button>
<button class="bg-slate-700 border-slate-600" type="button" @click="showModalDelete = false">Cancel</button>
</div>
</div>
</form>
</template>
</Modal>
</div>

View File

@ -1,39 +1,174 @@
<script setup>
import { X, GripVertical, MoreVertical, Edit3, CheckCircle2, Star } from 'lucide-vue-next';
import { Modal } from '@kouts/vue-modal'
import { ref } from 'vue';
import { X, GripVertical, MoreVertical, Edit3, CheckCircle2, Star, AlarmClock, Edit2 } from 'lucide-vue-next';
import DropdownMenu from 'v-dropdown-menu';
// import 'v-dropdown-menu/dist/vue3/v-dropdown-menu.css'
import { parseISO, isValid as isDateValid, format as formatDate,
isPast as isDatePast } from 'date-fns';
import VueDatePicker from '@vuepic/vue-datepicker';
import '@vuepic/vue-datepicker/dist/main.css'
const showModalDetails = ref(false);
const showModalEdit = ref(false);
const props = defineProps(['item', 'remove', 'currentProject', 'rename'])
const parsedDate = parseISO(props.item.dueDate ?? '');
const isValidDate = isDateValid(parsedDate);
// const formattedDate = formatDate(parsedDate, 'dd-MM-yyyy');
const dateEnabled = ref(isValidDate);
const date = ref(isValidDate ? new Date(props.item.dueDate) : null);
function editMe(event) {
event.preventDefault();
props.rename(event, props.item);
setTimeout(() => showModalEdit.value = false, 1);
}
</script>
<template>
<div class="flex items-center justify-between gap-2 *:block" :class="item.done ? 'line-through text-slate-300' : ''">
<p v-if="!item.done" class="handle text-xl cursor-pointer rounded-lg *:stroke-slate-400 *:stroke-1 hover:*:stroke-slate-300" title="Drag"><GripVertical /></p>
<p class="text-xl cursor-pointer rounded-lg *:stroke-slate-400 *:stroke-1 hover:*:stroke-slate-300"
:class="item.done ? '*:fill-blue-300 *:stroke-slate-800 hover:*:stroke-slate-700 hover:*:fill-blue-400 ' : ''" @click="$emit('toggleDone', props.item)" title="Toggle status"><CheckCircle2 /></p>
<p class="!overflow-hidden whitespace-nowrap text-ellipsis text-xl flex-1 cursor-pointer hover:bg-slate-600 rounded-lg p-1" :title="`${props.item.name} - show details`" @click="$emit('showDetails', props.item)">{{ props.item.name }}</p>
<div class="flex items-center justify-between gap-2 *:block" :class="(item.done ? 'line-through text-slate-300' : '')">
<p v-if="!item.done"
class="handle text-xl cursor-pointer rounded-lg
*:stroke-slate-400 *:stroke-1 hover:*:stroke-slate-300
active:cursor-move"
title="Drag">
<GripVertical />
</p>
<p class="text-xl cursor-pointer rounded-lg
*:stroke-slate-400 *:stroke-1 hover:*:stroke-slate-300"
:class="item.done ? '*:fill-blue-300 *:stroke-slate-800 hover:*:stroke-slate-700 hover:*:fill-blue-400 ' : ''"
@click="$emit('toggleDone', props.item)"
title="Toggle status">
<CheckCircle2 />
</p>
<p class="!overflow-hidden whitespace-nowrap
text-ellipsis text-xl flex-1 cursor-pointer
hover:bg-slate-600 rounded-lg p-1"
:title="`${props.item.name} - show details`"
@click="showModalDetails = true"
:class="showModalDetails ? 'bg-slate-500/50 hover:bg-slate-500/75' : ''">
{{ props.item.name }}
</p>
<p class="!overflow-hidden whitespace-nowrap
text-ellipsis text-sm !flex items-center gap-1
[&_svg]:w-5 text-slate-200"
:title="`${props.item.name} - show details`">
<AlarmClock v-if="props.item.dueDate" :class="isDatePast(new Date(props.item.dueDate)) ? 'text-red-300' : ''" />
<p :class="isDatePast(new Date(props.item.dueDate)) ? 'text-red-300' : ''">
{{ props.item.dueDate ? formatDate(props.item.dueDate, `MMM do ${new Date(props.item.dueDate).getFullYear() == new Date().getFullYear() ? "" : "yyyy"}`) : '' }}
</p>
</p>
<p class="text-xl cursor-pointer rounded-lg *:stroke-slate-400
*:stroke-1 hover:*:stroke-slate-300"
:class="item.priority ? '*:fill-yellow-300 *:stroke-0 hover:*:fill-yellow-400 ' : ''"
@click="$emit('togglePriority', props.item)"
title="Toggle priority">
<Star />
</p>
<dropdown-menu
class="relative rounded-full hover:bg-slate-500
*:hover:[&_button]:*:stroke-slate-300"
title="More">
<p class="text-xl cursor-pointer rounded-lg *:stroke-slate-400 *:stroke-1 hover:*:stroke-slate-300"
:class="item.priority ? '*:fill-yellow-300 *:stroke-0 hover:*:fill-yellow-400 ' : ''" @click="$emit('togglePriority', props.item)" title="Toggle priority"><Star /></p>
<!-- <button class="cursor-pointer hover:bg-slate-600 rounded-lg p-1" type="button" @click="props.remove(item)"><X /></button> -->
<dropdown-menu class="relative rounded-full hover:bg-slate-500" title="More">
<template #trigger>
<button class="p-2"><MoreVertical /></button>
<button
class="p-2 *:stroke-slate-400
hover:*:stroke-slate-300">
<MoreVertical />
</button>
</template>
<template #body>
<ul class="*:flex absolute right-0 bg-slate-600 p-2 rounded-lg flex flex-col gap-1 *:cursor-pointer *:p-1 *:rounded border border-slate-500">
<li class="hover:bg-slate-500" @click="props.rename(item)"><Edit3 /> Rename</li>
<li class="hover:bg-slate-500" @click="props.remove(item)"><X /> Delete</li>
<ul class="*:flex absolute right-0
bg-slate-600 p-2 rounded-lg flex flex-col
gap-1 *:cursor-pointer *:p-1
*:rounded border border-slate-500 z-10">
<li class="hover:bg-slate-500"
@click="showModalEdit = true">
<Edit2 /> Edit
</li>
<li class="hover:bg-slate-500"
@click="props.remove(item)">
<X /> Delete
</li>
</ul>
</template>
</dropdown-menu>
</div>
<Modal v-model="showModalDetails" title="Task details">
<template v-slot:default>
<form>
<div class="form-group">
<label for="inputEmail4" class="text-2xl block font-bold">{{ props.item.name }}</label>
</div>
<div class="form-group">
<label for="inputEmail4" class="text-lg block">{{ props.item.description ?? '(no description)' }}</label>
</div>
<div class="form-group">
<label for="inputEmail4" class="text-xl flex items-center gap-1 " :class="isDatePast(new Date(props.item.dueDate)) ? 'text-red-300' : ''">
<AlarmClock :class="isDatePast(new Date(props.item.dueDate)) ? 'text-red-300' : ''" /> {{ props.item.dueDate ? formatDate(props.item.dueDate, "MMM do yyyy, HH:mm") : '(no due date)' }}
</label>
</div>
<div class="row modal-footer">
<div class="*:p-2 *:rounded-lg *:border flex gap-2 mt-3">
<button class="bg-yellow-600/75 font-bold border-slate-600" type="button" @click="showModalEdit = true">Edit</button>
<button class="bg-slate-700 border-slate-600" type="button" @click="showModalDetails = false">Close</button>
</div>
</div>
</form>
</template>
</Modal>
<Modal v-model="showModalEdit" title="Edit task">
<template v-slot:default>
<form method="post" @submit="function (e) {editMe(e)}">
<div class="form-group">
<label for="inputEmail4" class="text-xl block">Name</label>
<input id="inputEmail4" type="text" class="block bg-slate-700 text-lg p-1 rounded-lg border border-slate-600" placeholder="Name" required name="name" :value="props.item.name" />
</div>
<div class="form-group">
<label for="add-task-description" class="text-xl block">Description</label>
<textarea id="add-task-description" type="text" class="block bg-slate-700 text-lg p-1 rounded-lg border border-slate-600" placeholder="Description" name="description" :value="props.item.description"></textarea>
</div>
<div class="form-group">
<label for="add-task-description" class="text-xl block">Due date</label>
<input type="checkbox" v-model="dateEnabled" name="due-date-check" id="due-date-check" :value="props.item.dueDate ?? false"> <label for="due-date-check">Enabled</label>
<VueDatePicker v-model="date" :min-date="new Date()" :dark="true" :disabled="!dateEnabled" :required="dateEnabled" name="vuedate"></VueDatePicker>
<!-- {{ date.toISOString() }} -->
</div>
<div class="row modal-footer">
<div class="*:p-2 *:rounded-lg *:border flex gap-2 mt-3">
<button class="bg-green-800 border-green-600" type="submit">Edit</button>
<button class="bg-red-900 border-red-700" type="button" @click="showModalEdit = false">Discard</button>
</div>
</div>
</form>
</template>
</Modal>
</template>
<style scoped>

View File

@ -5,23 +5,61 @@ import App from './App.vue'
import { Project as ProjectClass } from './project.js';
import DropdownMenu from 'v-dropdown-menu'
console.log(JSON.parse(localStorage.getItem("tasks-projects-todo")));
JSON.parse(localStorage.getItem("tasks-projects-todo")).forEach(element => {
let proj = ProjectClass.newProject(element.name);
element.tasks.forEach(task => {
// console.log(task)
proj.newTask(
task.name,
task.priority,
task.dueDate,
task.description,
task.done,
)
})
});
import { Modal } from '@kouts/vue-modal'
import './vue-modal-dialog.css'
const app = createApp(App)
app.use(DropdownMenu)
function defaultProjects() {
function newTask(project, name, priority, dueDate, description, done) {
if (priority == undefined) {
project.newTask(name);
} else {
project.newTask(name, priority, dueDate, description, done);
}
}
let study = ProjectClass.newProject("Study");
newTask(study, "Learn JS", true, new Date(2024, 10, 10, 10, 10, 10).toISOString(), "Deskrypcja!", false);
newTask(study, "Attend school", false, null, null, true);
newTask(study, "Study for Math exam", false, null, null, true);
let work = ProjectClass.newProject("Work");
newTask(work, "Complete To-Do project", true, null, null, false);
newTask(work, "Prepare for work practice", true, null, null, false);
let travel = ProjectClass.newProject("Travel");
newTask(travel, "Visit Amsterdam");
newTask(travel, "Visit Riga and Tallinn", false, null, null, true);
newTask(travel, "Go to Sudets");
let banking = ProjectClass.newProject("Banking");
newTask(banking, "Withdraw money from savings", false, null, null, true);
newTask(banking, "Pay off loan");
newTask(banking, "Register for cashback services", true, null, null, true);
}
if (localStorage.getItem("tasks-projects-todo")) {
JSON.parse(localStorage.getItem("tasks-projects-todo")).forEach(element => {
let proj = ProjectClass.newProject(element.name);
element.tasks.forEach(task => {
proj.newTask(
task.name,
task.priority,
task.dueDate,
task.description,
task.done,
)
})
});
} else {
defaultProjects();
}
import VueDatePicker from '@vuepic/vue-datepicker';
import '@vuepic/vue-datepicker/dist/main.css'
const app = createApp(App);
app.use(DropdownMenu);
app.component('Modal', Modal);
app.component('VueDatePicker', VueDatePicker);
// import router from './router'
// app.use(router)

View File

@ -1,4 +1,4 @@
import { toRaw } from "vue";
import { defineAsyncComponent, toRaw } from "vue";
import { currentProject } from "./current.js";
class Task {
@ -12,6 +12,12 @@ class Task {
changeName(name) {
this.name = name;
}
changeDescription(description) {
this.description = description;
}
changeDueDate(dueDate) {
this.dueDate = dueDate;
}
toggleDone() {
this.done = !this.done;
}

View File

@ -3,13 +3,13 @@ import { ref, onMounted, toRaw, reactive } from 'vue'
import draggable from 'vuedraggable'
import { Plus, X } from 'lucide-vue-next';
import Project from '../instances/Project.vue';
import { Project as ProjectClass } from "../project.js";
import { publishToLocalStorage } from '../localStorage.js';
import { currentProject } from '../current.js';
import { Modal } from '@kouts/vue-modal'
const showModalAddProject = ref(false)
let list = ref(ProjectClass.projects)
@ -20,30 +20,33 @@ function refreshProjects() {
publishToLocalStorage();
}
function newProject() {
let projName = prompt("Project name:");
if (projName != null && projName != "") {
ProjectClass.newProject(projName);
}
function newProject(event) {
event.preventDefault();
let form = new FormData(event.srcElement);
ProjectClass.newProject(form.values().next().value);
setTimeout(() => showModalAddProject.value = false, 1)
// let projName = prompt("Project name:");
// if (projName != null && projName != "") {
// ProjectClass.newProject(projName);
// }
refreshProjects();
}
function renameProject(item) {
let newName = prompt(`Rename ${item.name} to:`);
if (newName != null && newName != "") {
item.changeName(newName);
}
function renameProject(item, newName) {
// let newName = prompt(`Rename ${item.name} to:`);
// if (newName != null && newName != "") {
item.changeName(newName);
// }
refreshProjects();
}
function removeProject(item) {
if (confirm(`Are you sure you want to remove project ${item.name}?`)) {
if (currentProject.value == reactive(toRaw(item))) {
currentProject.value = null;
}
item.remove();
// if (confirm(`Are you sure you want to remove project ${item.name}?`)) {
if (currentProject.value == reactive(toRaw(item))) {
currentProject.value = null;
}
item.remove();
// }
refreshProjects();
}
@ -58,9 +61,26 @@ function onMoveCallback(){
<div class="projects lg:w-[1000px] lg:ml-auto lg:mr-auto m-3 p-3 bg-slate-700 rounded-md border border-slate-600">
<div class="flex justify-center">
<button type="button" @click="newProject()">
<button type="button" @click="showModalAddProject = true">
<div class="flex items-center gap-1 rounded-xl border p-1 hover:border-slate-500 hover:bg-slate-600 border-slate-600 bg-slate-700"><Plus /> <p>Add project</p></div>
</button>
<Modal v-model="showModalAddProject" title="Add project">
<template v-slot:default>
<form method="post" @submit="function(event) {newProject(event)}">
<div class="form-group">
<label for="inputEmail4" class="text-xl block">Name</label>
<input id="inputEmail4" type="text" class="block bg-slate-700 text-lg p-1 rounded-lg border border-slate-600" placeholder="Name" required name="name" />
</div>
<div class="row modal-footer">
<div class="*:p-2 *:rounded-lg *:border flex gap-2 mt-3">
<button class="bg-green-800 border-green-600" type="submit">Add</button>
<button class="bg-red-900 border-red-700" type="button" @click="showModalAddProject = false">Discard</button>
</div>
</div>
</form>
</template>
</Modal>
</div>
<draggable
v-if="list.length > 0"
@ -72,12 +92,12 @@ function onMoveCallback(){
handle=".handle"
animation="150">
<template #item="{element}">
<Project @change-current-project="(item) => {currentProject == reactive(toRaw(item)) ? currentProject = null : currentProject = toRaw(item); console.log(currentProject)}" :item="element" :remove="removeProject" :rename="renameProject" />
<Project @change-current-project="(item) => {currentProject == reactive(toRaw(item)) ? currentProject = null : currentProject = toRaw(item); console.log(currentProject)}" :item="element" :remove="removeProject" :rename="renameProject" :current-project="currentProject" />
</template>
</draggable>
<div v-else class="text-2xl font-light text-center">No projects. Add one!</div>
</div>
</div>
</template>
<style scoped>

View File

@ -9,11 +9,16 @@ import Task from '../instances/Task.vue';
import { currentProject } from '../current.js';
import { publishToLocalStorage } from '../localStorage.js';
import { Modal } from '@kouts/vue-modal'
const showModalAddTask = ref(false)
const showModalNoProject = ref(false)
let list = ref([])
function refreshTasks() {
checkIfAtLeastOneDone();
list.value = [];
if (currentProject.value != null) {
list.value.push(...currentProject.value.tasks);
@ -23,15 +28,37 @@ function refreshTasks() {
refreshTasks();
function newTask() {
if (currentProject.value != null) {
let taskName = prompt("Task name:");
if (taskName != null && taskName != "") {
toRaw(currentProject.value).newTask(taskName);
}
function newTask(event) {
event.preventDefault();
let form = new FormData(event.srcElement);
let values = form.values();
let item = toRaw(currentProject.value).newTask(values.next().value);
item.changeDescription(values.next().value.trim());
setTimeout(() => showModalAddTask.value = false, 1);
refreshTasks();
}
function editTask(event, item) {
event.preventDefault();
let form = new FormData(event.srcElement);
let values = form.values();
item.changeName(values.next().value.trim());
let desc = values.next().value.trim();
if (desc != "") {
item.changeDescription(desc);
} else {
alert("No project selected!")
item.changeDescription(null);
}
values.next();
let dateDate = values.next().value;
if (dateDate) {
let testDate = new Date(dateDate).toISOString();
item.changeDueDate(testDate);
console.log(testDate);
} else {
item.changeDueDate(null);
}
refreshTasks();
}
@ -49,7 +76,6 @@ function onMoveCallback(){
watch(currentProject, (test) => {
console.log(test);
refreshTasks();
})
@ -63,8 +89,14 @@ function togglePriority(item) {
refreshTasks();
}
const completedShow = ref(false);
const completedLength = ref(0);
function checkIfAtLeastOneDone() {
return currentProject.value.tasks.some(element => element.done);
if (currentProject.value != null) {
completedShow.value = currentProject.value.tasks.some(element => element.done);
completedLength.value = currentProject.value.tasks.filter(element => element.done).length;
}
}
@ -74,32 +106,71 @@ function checkIfAtLeastOneDone() {
<div class="tasks lg:w-[1000px] lg:ml-auto lg:mr-auto m-3 p-3 bg-slate-700 rounded-md border border-slate-600">
<div class="flex justify-center">
<button type="button" @click="newTask()">
<button type="button" @click="currentProject != null ? showModalAddTask = true : showModalNoProject = true">
<div class="flex items-center gap-1 rounded-xl border p-1 hover:border-slate-500 hover:bg-slate-600 border-slate-600 bg-slate-700"><Plus /> <p>Add task</p></div>
</button>
<Modal v-model="showModalAddTask" title="Add task">
<template v-slot:default>
<form method="post" @submit="function(event) {newTask(event)}">
<div class="form-group">
<label for="inputEmail4" class="text-xl block">Name</label>
<input id="inputEmail4" type="text" class="block bg-slate-700 text-lg p-1 rounded-lg border border-slate-600" placeholder="Name" required name="name" />
</div>
<div class="form-group">
<label for="add-task-description" class="text-xl block">Description</label>
<input id="add-task-description" type="text" class="block bg-slate-700 text-lg p-1 rounded-lg border border-slate-600" placeholder="Description" name="description" />
</div>
<div class="row modal-footer">
<div class="*:p-2 *:rounded-lg *:border flex gap-2 mt-3">
<button class="bg-green-800 border-green-600" type="submit">Add</button>
<button class="bg-red-900 border-red-700" type="button" @click="showModalAddTask = false">Discard</button>
</div>
</div>
</form>
</template>
</Modal>
<Modal v-model="showModalNoProject" title="Add task">
<template v-slot:default>
<form>
<div class="form-group">
<label class="text-2xl block">No project selected</label>
</div>
<div class="row modal-footer">
<div class="*:p-2 *:rounded-lg *:border flex gap-2 mt-3">
<button class="bg-slate-700 border-slate-600" type="button" @click="showModalNoProject = false">Close</button>
</div>
</div>
</form>
</template>
</Modal>
</div>
<div
v-if="list.length > 0">
<draggable
v-model="list"
@start="drag=true"
@end="drag=false"
item-key="id"
@change="onMoveCallback()"
handle=".handle"
animation="150">
<template #item="{element}">
<Task :item="element" v-if="!element.done" :remove="removeTask" @toggle-done="toggleDone" @toggle-priority="togglePriority" />
</template>
</draggable>
<!-- <div> -->
<draggable
v-model="list"
@start="drag=true"
@end="drag=false"
item-key="id"
@change="onMoveCallback()"
handle=".handle"
animation="150">
<!-- class="grid grid-cols-task-grid"> -->
<template #item="{element}">
<Task :item="element" v-if="!element.done" :rename="editTask" :remove="removeTask" @toggle-done="toggleDone" @toggle-priority="togglePriority" />
</template>
</draggable>
<!-- </div> -->
<dropdown-menu class="relative rounded-full" title="More" :closeOnClickOutside="false">
<template #trigger>
<h1 v-if="checkIfAtLeastOneDone()" class="flex cursor-pointer text-slate-300"><ChevronDown /> Completed</h1>
<h1 v-if="completedShow" class="flex cursor-pointer text-slate-300 text-lg items-center gap-1 mt-2 mb-1"><ChevronDown /> <p>Completed ({{ completedLength }})</p></h1>
</template>
<template #body>
<div v-for="element in list">
<Task :item="element" v-if="element.done" :remove="removeTask" @toggle-done="toggleDone" @toggle-priority="togglePriority" />
<Task :item="element" v-if="element.done" :rename="editTask" :remove="removeTask" @toggle-done="toggleDone" @toggle-priority="togglePriority" />
</div>
</template>
</dropdown-menu>

94
src/vue-modal-dialog.css Normal file
View File

@ -0,0 +1,94 @@
.vm-backdrop {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: #00000080;
}
.vm-wrapper {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow-x: hidden;
overflow-y: auto;
outline: 0;
}
.vm {
position: relative;
min-width: 300px;
width: max-content;
@apply bg-slate-800/75 rounded-lg cursor-default top-8 mx-auto my-0;
box-shadow: 0 5px 15px #00000080;
}
.vm-titlebar {
padding: 10px 15px;
overflow: auto;
border-bottom: 1px solid #e5e5e5;
}
.vm-title {
margin-top: 2px;
margin-bottom: 0;
display: inline-block;
font-size: 18px;
font-weight: 400;
}
.vm-btn-close {
color: #ccc;
padding: 0;
cursor: pointer;
background: 0 0;
border: 0;
float: right;
font-size: 24px;
line-height: 1em;
}
.vm-btn-close:before {
content: "×";
font-family: Arial;
}
.vm-btn-close:hover,
.vm-btn-close:focus,
.vm-btn-close:focus:hover {
color: #bbb;
border-color: transparent;
background-color: transparent;
}
.vm-content {
padding: 10px 15px 15px;
}
.vm-content .full-hr {
width: auto;
border: 0;
border-top: 1px solid #e5e5e5;
margin: 15px -14px;
}
.vm-fadeIn {
animation-name: vm-fadeIn;
}
@keyframes vm-fadeIn {
0% {
opacity: 0;
}
to {
opacity: 1;
}
}
.vm-fadeOut {
animation-name: vm-fadeOut;
}
@keyframes vm-fadeOut {
0% {
opacity: 1;
}
to {
opacity: 0;
}
}
.vm-fadeIn,
.vm-fadeOut {
animation-duration: 0.25s;
animation-fill-mode: both;
}

View File

@ -4,7 +4,11 @@ export default {
'./src/**/*.{html,js,vue}',
],
theme: {
extend: {},
extend: {
gridTemplateColumns: {
'task-grid': 'repeat(auto-fill, minmax(300px, 1fr))',
}
},
},
plugins: [],
}