kanban style app
This commit is contained in:
+23
-68
@@ -1,74 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="button-group">
|
<header>
|
||||||
<MyButton size="sm">
|
<h1>Task Board</h1>
|
||||||
Primary Small
|
</header>
|
||||||
</MyButton>
|
<main class="three-col-layout">
|
||||||
<MyButton>
|
<section id="todo-section" aria-labelledby="todo-header">
|
||||||
Primary Medium
|
<h2 id="todo-header">To-do</h2>
|
||||||
</MyButton>
|
<TaskList id="todo-list" :tasks="todoTasks" />
|
||||||
<MyButton size="lg">
|
</section>
|
||||||
Primary Large
|
<section id="inprogress-section" aria-labelledby="inprogress-header">
|
||||||
</MyButton>
|
<h2 id="inprogress-header">In-progress</h2>
|
||||||
</div>
|
<TaskList id="inprogress-list" :tasks="inProgressTasks" />
|
||||||
|
</section>
|
||||||
<div class="button-group">
|
<section id="done-section" aria-labelledby="done-header">
|
||||||
<MyButton variant="secondary"
|
<h2 id="done-header">Done</h2>
|
||||||
size="sm">
|
<TaskList id="done-list" :tasks="doneTasks" />
|
||||||
Secondary Small
|
</section>
|
||||||
</MyButton>
|
</main>
|
||||||
<MyButton variant="secondary">
|
<footer></footer>
|
||||||
Secondary Medium
|
|
||||||
</MyButton>
|
|
||||||
<MyButton variant="secondary"
|
|
||||||
size="lg">
|
|
||||||
Secondary Large
|
|
||||||
</MyButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="button-group">
|
|
||||||
<MyButton variant="danger"
|
|
||||||
size="sm">
|
|
||||||
Danger Small
|
|
||||||
</MyButton>
|
|
||||||
<MyButton variant="danger">
|
|
||||||
Danger Medium
|
|
||||||
</MyButton>
|
|
||||||
<MyButton variant="danger"
|
|
||||||
size="lg">
|
|
||||||
Danger Large
|
|
||||||
</MyButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="button-group">
|
|
||||||
<MyButton variant="danger"
|
|
||||||
size="lg"
|
|
||||||
:loading="true">
|
|
||||||
Danger Large
|
|
||||||
</MyButton>
|
|
||||||
<MyButton size="lg"
|
|
||||||
:disabled="true">
|
|
||||||
Disabled Large
|
|
||||||
</MyButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="button-group">
|
|
||||||
<MyButton size="lg"
|
|
||||||
:fullwidth="true">
|
|
||||||
Primary Fullwidth
|
|
||||||
</MyButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import MyButton from './components/MyButton.vue';
|
import { storeToRefs } from "pinia";
|
||||||
|
import { useTasksStore } from "./stores/tasks";
|
||||||
|
import TaskList from "./components/TaskList.vue";
|
||||||
|
|
||||||
|
const tasksStore = useTasksStore();
|
||||||
|
const { todoTasks, inProgressTasks, doneTasks } = storeToRefs(tasksStore);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.button-group {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
margin-block-start: 1rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="task.assignee">
|
||||||
|
<dt>Assignee:</dt>
|
||||||
|
<dd>
|
||||||
|
<form :id="`${task.id}-assignee-form`">
|
||||||
|
<select
|
||||||
|
:id="`${task.id}-assignee-select`"
|
||||||
|
:value="task.assignee.id"
|
||||||
|
@change="updateTaskAssignee(task.id, $event)"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="user in users"
|
||||||
|
:key="user.id"
|
||||||
|
:value="user.id"
|
||||||
|
>
|
||||||
|
{{ user.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</form>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useTasksStore } from "@/stores/tasks";
|
||||||
|
import type { TaskItem } from "@/types";
|
||||||
|
|
||||||
|
interface TaskAssigneeProps {
|
||||||
|
task: TaskItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { task } = defineProps<TaskAssigneeProps>();
|
||||||
|
const { users, assignTask } = useTasksStore();
|
||||||
|
|
||||||
|
const updateTaskAssignee = (taskId: string, event: Event) => {
|
||||||
|
const target = event.target as HTMLSelectElement;
|
||||||
|
assignTask(taskId, target.value);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<dt>Priority:</dt>
|
||||||
|
<dd>
|
||||||
|
<form :id="`${task.id}-priority-form`">
|
||||||
|
<select
|
||||||
|
:id="`${task.id}-priority-select`"
|
||||||
|
:value="task.priority"
|
||||||
|
@change="updateTaskPriority(task.id, $event)"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="priority in TASK_PRIORITIES"
|
||||||
|
:key="priority"
|
||||||
|
:value="priority"
|
||||||
|
>
|
||||||
|
{{ priority }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</form>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useTasksStore } from "@/stores/tasks";
|
||||||
|
import { TASK_PRIORITIES, type TaskItem, type TaskPriority } from "@/types";
|
||||||
|
|
||||||
|
interface TaskAssigneeProps {
|
||||||
|
task: TaskItem;
|
||||||
|
}
|
||||||
|
const { task } = defineProps<TaskAssigneeProps>();
|
||||||
|
const { updatePriority } = useTasksStore();
|
||||||
|
|
||||||
|
const updateTaskPriority = (taskId: string, event: Event) => {
|
||||||
|
const target = event.target as HTMLSelectElement;
|
||||||
|
updatePriority(taskId, target.value as TaskPriority);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<dt>Status:</dt>
|
||||||
|
<dd>
|
||||||
|
<form :id="`${task.id}-status-form`">
|
||||||
|
<select
|
||||||
|
:id="`${task.id}-status-select`"
|
||||||
|
:value="task.status"
|
||||||
|
@change="updateTaskStatus(task.id, $event)"
|
||||||
|
>
|
||||||
|
<option v-for="status in TASK_STATUSES" :key="status">
|
||||||
|
{{ status }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</form>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useTasksStore } from "@/stores/tasks";
|
||||||
|
import { TASK_STATUSES, type TaskItem, type TaskStatus } from "@/types";
|
||||||
|
|
||||||
|
interface TaskAssigneeProps {
|
||||||
|
task: TaskItem;
|
||||||
|
}
|
||||||
|
const { task } = defineProps<TaskAssigneeProps>();
|
||||||
|
const { updateStatus } = useTasksStore();
|
||||||
|
|
||||||
|
const updateTaskStatus = (taskId: string, event: Event) => {
|
||||||
|
const target = event.target as HTMLSelectElement;
|
||||||
|
updateStatus(taskId, target.value as TaskStatus);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<ul id="todo-list" class="card-list">
|
||||||
|
<TaskListItem v-for="task in tasks" :key="task.id" :task="task" />
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { TaskItem } from "@/types";
|
||||||
|
import TaskListItem from "./TaskListItem.vue";
|
||||||
|
|
||||||
|
interface TaskListProps {
|
||||||
|
tasks: TaskItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tasks } = defineProps<TaskListProps>();
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<template>
|
||||||
|
<li
|
||||||
|
class="card"
|
||||||
|
:class="{
|
||||||
|
'priority-high': task.priority === 'high',
|
||||||
|
'priority-medium': task.priority === 'medium',
|
||||||
|
'priority-low': task.priority === 'low',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>{{ task.title }}</h3>
|
||||||
|
</div>
|
||||||
|
<dl>
|
||||||
|
<AssigneeDetails :task />
|
||||||
|
<StatusDetails :task />
|
||||||
|
<PriorityDetails :task />
|
||||||
|
<div>
|
||||||
|
<dt>Created At:</dt>
|
||||||
|
<dd>{{ formattedCreatedAt }}</dd>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt>Updated At:</dt>
|
||||||
|
<dd>{{ formattedUpdatedAt }}</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from "vue";
|
||||||
|
import type { TaskItem } from "@/types";
|
||||||
|
import AssigneeDetails from "./Details/AssigneeDetails.vue";
|
||||||
|
import StatusDetails from "./Details/StatusDetails.vue";
|
||||||
|
import PriorityDetails from "./Details/PriorityDetails.vue";
|
||||||
|
|
||||||
|
interface TaskListItemProps {
|
||||||
|
task: TaskItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { task } = defineProps<TaskListItemProps>();
|
||||||
|
|
||||||
|
const formatterOptions = {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "numeric",
|
||||||
|
second: "numeric",
|
||||||
|
timeZoneName: "short",
|
||||||
|
timeZone: "America/New_York",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const formattedCreatedAt = computed(() =>
|
||||||
|
new Intl.DateTimeFormat("en-US", formatterOptions).format(
|
||||||
|
new Date(task.createdAt),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const formattedUpdatedAt = computed(() =>
|
||||||
|
new Intl.DateTimeFormat("en-US", formatterOptions).format(
|
||||||
|
new Date(task.updatedAt),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
</script>
|
||||||
+10
-8
@@ -1,12 +1,14 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from "vue";
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from "pinia";
|
||||||
|
|
||||||
import App from './App.vue'
|
import App from "./App.vue";
|
||||||
import router from './router'
|
import router from "./router";
|
||||||
|
|
||||||
const app = createApp(App)
|
import "./styles/main.css";
|
||||||
|
|
||||||
app.use(createPinia())
|
const app = createApp(App);
|
||||||
app.use(router)
|
|
||||||
|
|
||||||
app.mount('#app')
|
app.use(createPinia());
|
||||||
|
app.use(router);
|
||||||
|
|
||||||
|
app.mount("#app");
|
||||||
|
|||||||
@@ -0,0 +1,221 @@
|
|||||||
|
import { ref, computed } from "vue";
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
TaskItem,
|
||||||
|
TaskStatus,
|
||||||
|
TaskPriority,
|
||||||
|
TaskTag,
|
||||||
|
TaskUser,
|
||||||
|
} from "@/types";
|
||||||
|
|
||||||
|
export const useTasksStore = defineStore("tasks", () => {
|
||||||
|
const users = ref<TaskUser[]>([
|
||||||
|
{ id: "user-1", name: "Ryan Trimble" },
|
||||||
|
{ id: "user-2", name: "Vickie Chinnick" },
|
||||||
|
{ id: "user-3", name: "James Okafor" },
|
||||||
|
{ id: "user-4", name: "Sara Mendez" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const tags = ref<TaskTag[]>([
|
||||||
|
{ id: "tag-1", name: "project-1" },
|
||||||
|
{ id: "tag-2", name: "project-2" },
|
||||||
|
{ id: "tag-3", name: "bug" },
|
||||||
|
{ id: "tag-4", name: "feature" },
|
||||||
|
{ id: "tag-5", name: "design" },
|
||||||
|
{ id: "tag-6", name: "devops" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const tasks = ref<TaskItem[]>([
|
||||||
|
{
|
||||||
|
id: "task-1",
|
||||||
|
title: "Set up CI/CD pipeline",
|
||||||
|
assignee: { id: "user-3", name: "James Okafor" },
|
||||||
|
status: "done",
|
||||||
|
priority: "high",
|
||||||
|
tags: [{ id: "tag-6", name: "devops" }],
|
||||||
|
createdAt: "2026-05-10T08:00:00Z",
|
||||||
|
updatedAt: "2026-05-14T10:30:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "task-2",
|
||||||
|
title: "Design onboarding flow mockups",
|
||||||
|
assignee: { id: "user-4", name: "Sara Mendez" },
|
||||||
|
status: "done",
|
||||||
|
priority: "medium",
|
||||||
|
tags: [{ id: "tag-5", name: "design" }],
|
||||||
|
createdAt: "2026-05-11T09:00:00Z",
|
||||||
|
updatedAt: "2026-05-15T14:00:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "task-3",
|
||||||
|
title: "Implement authentication module",
|
||||||
|
assignee: { id: "user-1", name: "Ryan Trimble" },
|
||||||
|
status: "in-progress",
|
||||||
|
priority: "high",
|
||||||
|
tags: [
|
||||||
|
{ id: "tag-4", name: "feature" },
|
||||||
|
{ id: "tag-1", name: "project-1" },
|
||||||
|
],
|
||||||
|
createdAt: "2026-05-12T10:00:00Z",
|
||||||
|
updatedAt: "2026-05-17T09:15:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "task-4",
|
||||||
|
title: "Fix broken pagination on dashboard",
|
||||||
|
assignee: { id: "user-2", name: "Vickie Chinnick" },
|
||||||
|
status: "in-progress",
|
||||||
|
priority: "high",
|
||||||
|
tags: [{ id: "tag-3", name: "bug" }],
|
||||||
|
createdAt: "2026-05-13T11:00:00Z",
|
||||||
|
updatedAt: "2026-05-17T08:00:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "task-5",
|
||||||
|
title: "Write unit tests for task store",
|
||||||
|
assignee: { id: "user-1", name: "Ryan Trimble" },
|
||||||
|
status: "in-progress",
|
||||||
|
priority: "medium",
|
||||||
|
tags: [{ id: "tag-1", name: "project-1" }],
|
||||||
|
createdAt: "2026-05-14T12:00:00Z",
|
||||||
|
updatedAt: "2026-05-16T16:45:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "task-6",
|
||||||
|
title: "Add dark mode support",
|
||||||
|
assignee: { id: "user-4", name: "Sara Mendez" },
|
||||||
|
status: "to-do",
|
||||||
|
priority: "low",
|
||||||
|
tags: [
|
||||||
|
{ id: "tag-5", name: "design" },
|
||||||
|
{ id: "tag-4", name: "feature" },
|
||||||
|
],
|
||||||
|
createdAt: "2026-05-15T08:30:00Z",
|
||||||
|
updatedAt: "2026-05-15T08:30:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "task-7",
|
||||||
|
title: "Migrate database to PostgreSQL",
|
||||||
|
assignee: { id: "user-3", name: "James Okafor" },
|
||||||
|
status: "to-do",
|
||||||
|
priority: "high",
|
||||||
|
tags: [
|
||||||
|
{ id: "tag-6", name: "devops" },
|
||||||
|
{ id: "tag-2", name: "project-2" },
|
||||||
|
],
|
||||||
|
createdAt: "2026-05-15T09:00:00Z",
|
||||||
|
updatedAt: "2026-05-15T09:00:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "task-8",
|
||||||
|
title: "Audit and update dependencies",
|
||||||
|
assignee: { id: "user-2", name: "Vickie Chinnick" },
|
||||||
|
status: "to-do",
|
||||||
|
priority: "low",
|
||||||
|
tags: [{ id: "tag-6", name: "devops" }],
|
||||||
|
createdAt: "2026-05-16T10:00:00Z",
|
||||||
|
updatedAt: "2026-05-16T10:00:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "task-9",
|
||||||
|
title: "Fix null reference error on profile page",
|
||||||
|
assignee: { id: "user-1", name: "Ryan Trimble" },
|
||||||
|
status: "to-do",
|
||||||
|
priority: "high",
|
||||||
|
tags: [
|
||||||
|
{ id: "tag-3", name: "bug" },
|
||||||
|
{ id: "tag-2", name: "project-2" },
|
||||||
|
],
|
||||||
|
createdAt: "2026-05-17T07:00:00Z",
|
||||||
|
updatedAt: "2026-05-17T07:00:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "task-10",
|
||||||
|
title: "Document REST API endpoints",
|
||||||
|
assignee: { id: "user-4", name: "Sara Mendez" },
|
||||||
|
status: "to-do",
|
||||||
|
priority: "medium",
|
||||||
|
tags: [
|
||||||
|
{ id: "tag-1", name: "project-1" },
|
||||||
|
{ id: "tag-2", name: "project-2" },
|
||||||
|
],
|
||||||
|
createdAt: "2026-05-17T08:00:00Z",
|
||||||
|
updatedAt: "2026-05-17T08:00:00Z",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const findTask = (taskId: string): TaskItem | undefined =>
|
||||||
|
tasks.value.find((task) => task.id === taskId);
|
||||||
|
|
||||||
|
const findUser = (userId: string): TaskUser | undefined =>
|
||||||
|
users.value.find((user) => user.id === userId);
|
||||||
|
|
||||||
|
const updateTimestamp = (task: TaskItem) => {
|
||||||
|
task.updatedAt = new Date().toISOString();
|
||||||
|
};
|
||||||
|
|
||||||
|
const addTask = (newTask: TaskItem) => {
|
||||||
|
if (!newTask) return;
|
||||||
|
|
||||||
|
tasks.value = [...tasks.value, newTask];
|
||||||
|
};
|
||||||
|
|
||||||
|
const assignTask = (taskId: string, userId: string) => {
|
||||||
|
const task = findTask(taskId);
|
||||||
|
const user = findUser(userId);
|
||||||
|
|
||||||
|
if (task && task.assignee) {
|
||||||
|
task.assignee = user as TaskUser;
|
||||||
|
updateTimestamp(task);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateStatus = (taskId: string, newStatus: TaskStatus) => {
|
||||||
|
const task = findTask(taskId);
|
||||||
|
if (!task) return;
|
||||||
|
|
||||||
|
task.status = newStatus;
|
||||||
|
updateTimestamp(task);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatePriority = (taskId: string, newPriority: TaskPriority) => {
|
||||||
|
const task = findTask(taskId);
|
||||||
|
if (!task) return;
|
||||||
|
|
||||||
|
task.priority = newPriority;
|
||||||
|
updateTimestamp(task);
|
||||||
|
};
|
||||||
|
|
||||||
|
const todoTasks = computed(() => {
|
||||||
|
return tasks.value
|
||||||
|
.filter((task) => task.status === "to-do")
|
||||||
|
.sort((a, b) => a.id.localeCompare(b.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
const inProgressTasks = computed(() => {
|
||||||
|
return tasks.value
|
||||||
|
.filter((task) => task.status === "in-progress")
|
||||||
|
.sort((a, b) => a.id.localeCompare(b.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
const doneTasks = computed(() => {
|
||||||
|
return tasks.value
|
||||||
|
.filter((task) => task.status === "done")
|
||||||
|
.sort((a, b) => a.id.localeCompare(b.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
tags,
|
||||||
|
tasks,
|
||||||
|
users,
|
||||||
|
|
||||||
|
addTask,
|
||||||
|
assignTask,
|
||||||
|
updatePriority,
|
||||||
|
updateStatus,
|
||||||
|
|
||||||
|
todoTasks,
|
||||||
|
inProgressTasks,
|
||||||
|
doneTasks,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
height: 100svh;
|
||||||
|
overflow: clip;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
height: 60px;
|
||||||
|
display: grid;
|
||||||
|
place-content: center;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.three-col-layout {
|
||||||
|
display: grid;
|
||||||
|
height: 100%;
|
||||||
|
margin-inline: 1rem;
|
||||||
|
gap: 1rem;
|
||||||
|
@media screen and (min-width: 640px) {
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: calc(100vh - 150px);
|
||||||
|
list-style: "";
|
||||||
|
margin: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0;
|
||||||
|
scrollbar-gutter: stable;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
> * + * {
|
||||||
|
margin-block-start: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
margin-inline: 0.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dl {
|
||||||
|
> * {
|
||||||
|
margin-block-start: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dt {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.priority-high {
|
||||||
|
background-color: lightcoral;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.priority-medium {
|
||||||
|
background-color: lightgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.priority-low {
|
||||||
|
background-color: lightgoldenrodyellow;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
export const TASK_STATUSES = ["to-do", "in-progress", "done"] as const;
|
||||||
|
export const TASK_PRIORITIES = ["low", "medium", "high"] as const;
|
||||||
|
|
||||||
|
export type TaskStatus = (typeof TASK_STATUSES)[number];
|
||||||
|
export type TaskPriority = (typeof TASK_PRIORITIES)[number];
|
||||||
|
|
||||||
|
export interface TaskUser {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TaskTag {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TaskItem {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
status: TaskStatus;
|
||||||
|
assignee: TaskUser;
|
||||||
|
priority: TaskPriority;
|
||||||
|
tags?: TaskTag[];
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user