Skip to content

Commit 3b6bcea

Browse files
committed
exercise 6
1 parent 4b68b86 commit 3b6bcea

File tree

7 files changed

+255
-62
lines changed

7 files changed

+255
-62
lines changed

components.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ declare module '@vue/runtime-core' {
88
AppButton: typeof import('./src/components/AppButton.vue')['default']
99
AppImage: typeof import('./src/components/AppImage.vue')['default']
1010
AppImageDropzone: typeof import('./src/components/AppImageDropzone.vue')['default']
11+
AppPageHeading: typeof import('./src/components/AppPageHeading.vue')['default']
1112
BoardCard: typeof import('./src/components/BoardCard.vue')['default']
1213
BoardDragAndDrop: typeof import('./src/components/BoardDragAndDrop.vue')['default']
14+
BoardMenu: typeof import('./src/components/BoardMenu.vue')['default']
1315
RouterLink: typeof import('vue-router')['RouterLink']
1416
RouterView: typeof import('vue-router')['RouterView']
1517
TaskCard: typeof import('./src/components/TaskCard.vue')['default']
18+
TaskCreator: typeof import('./src/components/TaskCreator.vue')['default']
1619
TheAlerts: typeof import('./src/components/TheAlerts.vue')['default']
1720
TheDrawer: typeof import('./src/components/TheDrawer.vue')['default']
1821
TheNavbar: typeof import('./src/components/TheNavbar.vue')['default']

src/components/AppPageHeading.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<template>
2+
<h1 class="text-3xl mb-5"><slot></slot></h1>
3+
</template>
Lines changed: 71 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,99 @@
1-
<script setup>
2-
import { watch, reactive, toRaw } from "vue";
1+
<script setup lang="ts">
32
import { cloneDeep } from "lodash-es";
4-
import draggable from "vuedraggable";
3+
import { reactive, watch, toRaw } from "vue";
4+
import type { Board, Column, Task } from "@/types";
55
import { v4 as uuidv4 } from "uuid";
6+
import draggable from "vuedraggable";
7+
import TaskCard from "./TaskCard.vue";
8+
import { useAlerts } from "@/stores/alerts";
9+
const alerts = useAlerts();
10+
// props
11+
const props = defineProps<{
12+
board: Board;
13+
tasks: Task[];
14+
addTask(task: Partial<Task>): Task;
15+
}>();
616
7-
const props = defineProps({
8-
board: Object,
9-
tasks: Array,
10-
});
11-
12-
// emits
13-
const emit = defineEmits(["update"]);
17+
// events
18+
const emit = defineEmits<{
19+
(e: "update", payload: Partial<Board>): void;
20+
}>();
1421
1522
// local data
1623
const tasks = reactive(cloneDeep(props.tasks));
1724
const board = reactive(cloneDeep(props.board));
18-
const columns = reactive(JSON.parse(board.order));
25+
const columns = reactive<Column[]>(JSON.parse(board.order as string));
1926
20-
const addColumn = () =>
21-
columns.push({ id: uuidv4(), title: "New column", taskIds: [] });
27+
// methods
28+
function addColumn() {
29+
columns.push({
30+
id: uuidv4(),
31+
title: "New Column",
32+
taskIds: [],
33+
});
34+
}
2235
23-
watch(columns, () =>
36+
watch(columns, () => {
2437
emit(
2538
"update",
26-
cloneDeep({
27-
...props.board,
28-
order: JSON.stringify(toRaw(columns)),
29-
})
30-
)
31-
);
39+
cloneDeep({ ...board, order: JSON.stringify(toRaw(columns)) })
40+
);
41+
});
42+
43+
async function addTask({ column, title }: { column: Column; title: string }) {
44+
const newTask = { title };
45+
try {
46+
const savedTask = await props.addTask(newTask);
47+
tasks.push({ ...savedTask });
48+
column.taskIds.push(savedTask.id);
49+
} catch (error) {
50+
alerts.error("Error creating task!");
51+
}
52+
}
3253
</script>
3354

3455
<template>
35-
<button class="text-gray-500" @click="addColumn">New Column +</button>
36-
<div class="flex items-start py-12">
37-
<!-- //! outer block -->
56+
<div class="flex py-12 items-start">
3857
<draggable
3958
:list="columns"
4059
group="columns"
4160
item-key="id"
42-
class="flex flex-grow-0 flex-shrink-0 overflow-x-scroll"
61+
class="flex overflow-x-scroll flex-grow-0 flex-shrink-0"
4362
>
4463
<template #item="{ element: column }">
4564
<div
4665
class="column bg-gray-100 flex flex-col justify-between rounded-lg px-3 py-3 rounded mr-4 w-[300px]"
4766
>
48-
<h2>{{ column.title }}</h2>
49-
<!-- //! inner block -->
50-
<draggable
51-
:list="column.taskIds"
52-
group="tasks"
53-
item-key="uid"
54-
:animation="200"
55-
ghost-class="ghost-card"
56-
class="min-h-[400px]"
57-
>
58-
<template #item="{ element: taskId }">
59-
<task-card
60-
v-if="tasks.find((t) => t.id === taskId)"
61-
:task="tasks.find((t) => t.id === taskId)"
62-
class="mt-3 cursor-move"
63-
/>
64-
</template>
65-
</draggable>
67+
<div>
68+
<h3>{{ column.title }}</h3>
69+
<draggable
70+
:list="column.taskIds"
71+
group="tasks"
72+
item-key="uid"
73+
:animation="200"
74+
ghost-class="ghost-card"
75+
class="min-h-[400px]"
76+
>
77+
<template #item="{ element: taskId }">
78+
<TaskCard
79+
v-if="tasks.find((t: Task) => t.id === taskId)"
80+
:task="(tasks.find((t: Task) => t.id === taskId) as Task)"
81+
class="mt-3 cursor-move"
82+
/>
83+
</template>
84+
</draggable>
85+
<TaskCreator
86+
@create="
87+
addTask({
88+
column,
89+
title: $event,
90+
})
91+
"
92+
/>
93+
</div>
6694
</div>
6795
</template>
6896
</draggable>
97+
<button class="text-gray-500" @click="addColumn">New Column +</button>
6998
</div>
7099
</template>

src/components/BoardMenu.vue

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<script setup lang="ts">
2+
import { Popup as KPopup } from "@progress/kendo-vue-popup";
3+
import { Button as KButton } from "@progress/kendo-vue-buttons";
4+
import { ref } from "vue";
5+
import type { Board } from "@/types";
6+
const props = defineProps<{
7+
board: Board;
8+
}>();
9+
const show = ref(false);
10+
const menu = ref(null);
11+
defineEmits<{
12+
(e: "deleteBoard", payload: null): void;
13+
}>();
14+
</script>
15+
<template>
16+
<div>
17+
<KButton
18+
icon="folder"
19+
theme-color="primary"
20+
fillMode="outline"
21+
@click="show = !show"
22+
ref="button"
23+
>Show Menu</KButton
24+
>
25+
<KPopup
26+
:anchor="'button'"
27+
:anchor-align="{
28+
vertical: 'bottom',
29+
horizontal: 'right',
30+
}"
31+
:popup-align="{
32+
horizontal: 'right',
33+
vertical: 'top',
34+
}"
35+
:show="show"
36+
>
37+
<div class="p-5" ref="menu">
38+
<ul>
39+
<li class="text-red-500 whitespace-nowrap">
40+
<button @click="$emit('deleteBoard', null)">
41+
<span class="k-icon k-i-delete"></span>
42+
Delete Board
43+
</button>
44+
</li>
45+
</ul>
46+
</div>
47+
</KPopup>
48+
</div>
49+
</template>
50+
<style scoped>
51+
ul li {
52+
@apply p-2;
53+
border-bottom: 1px solid #eee;
54+
}
55+
ul li:last-of-type {
56+
border-bottom: none;
57+
}
58+
</style>

src/components/TaskCard.vue

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,46 @@
1-
<script setup>
2-
const props = defineProps({
3-
task: Object,
4-
});
1+
<script setup lang="ts">
2+
import type { Task } from "@/types";
3+
import {
4+
Card,
5+
CardTitle,
6+
CardHeader,
7+
Avatar,
8+
CardSubtitle,
9+
} from "@progress/kendo-vue-layout";
10+
import { Chip } from "@progress/kendo-vue-buttons";
11+
import { useDateFormat } from "@vueuse/core";
12+
const props = defineProps<{
13+
task: Task;
14+
}>();
15+
const date = useDateFormat(props.task.dueAt, "MM/DD");
516
</script>
6-
717
<template>
8-
<div class="p-5 bg-white">
9-
{{ task }}
10-
</div>
18+
<RouterLink :to="`/boards/${$route.params.id}/tasks/${task.id}`">
19+
<Card>
20+
<CardHeader>
21+
<div class="flex justify-between">
22+
<CardTitle>
23+
{{ task.title }}
24+
</CardTitle>
25+
<Avatar type="image" size="medium" shape="circle">
26+
<img
27+
style="width: 45px; height: 45px"
28+
src="https://pickaface.net/gallery/avatar/unr_sample_161118_2054_ynlrg.png"
29+
/>
30+
</Avatar>
31+
</div>
32+
33+
<CardSubtitle>
34+
<chip
35+
v-if="task.dueAt"
36+
:text="date"
37+
value="chip"
38+
rounded="full"
39+
icon="k-i-clock"
40+
theme-color="info"
41+
/>
42+
</CardSubtitle>
43+
</CardHeader>
44+
</Card>
45+
</RouterLink>
1146
</template>

src/components/TaskCreator.vue

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<script setup lang="ts">
2+
import { ref, nextTick } from "vue";
3+
import { Input as KInput } from "@progress/kendo-vue-inputs";
4+
const active = ref(false);
5+
const input = ref<HTMLInputElement | null>(null);
6+
const value = ref("");
7+
const emit = defineEmits<{
8+
(e: "create", payload: string): void;
9+
(e: "cancel"): void;
10+
}>();
11+
const handleActivate = () => {
12+
active.value = true;
13+
nextTick(() => input.value.focus());
14+
};
15+
const handleEnter = () => {
16+
emit("create", value.value);
17+
value.value = "";
18+
active.value = false;
19+
};
20+
const handleEsc = () => {
21+
value.value = "";
22+
emit("cancel");
23+
active.value = false;
24+
};
25+
</script>
26+
27+
<template>
28+
<div class="w-full">
29+
<KInput
30+
ref="input"
31+
type="text"
32+
v-if="active"
33+
v-model="value"
34+
@keypress.enter="handleEnter"
35+
@keypress.esc="handleEsc"
36+
@blur="handleEsc"
37+
/>
38+
<button
39+
v-else
40+
@click="handleActivate()"
41+
class="text-gray-600 block w-full text-left p-1"
42+
>
43+
+ Create Task
44+
</button>
45+
</div>
46+
</template>
47+
48+
<style scoped></style>

0 commit comments

Comments
 (0)