Default start, dialog modals, and more
This commit is contained in:
parent
fe864bb51a
commit
7180b51746
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
70
src/main.js
70
src/main.js
|
@ -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)
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -4,7 +4,11 @@ export default {
|
|||
'./src/**/*.{html,js,vue}',
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
extend: {
|
||||
gridTemplateColumns: {
|
||||
'task-grid': 'repeat(auto-fill, minmax(300px, 1fr))',
|
||||
}
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue