Skip to content

Commit b6199bb

Browse files
committed
initial commit
1 parent a8924a7 commit b6199bb

File tree

9 files changed

+3290
-0
lines changed

9 files changed

+3290
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
dist/

pnpm-lock.yaml

Lines changed: 2957 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app.ts

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { Task, Status } from "./types.js";
2+
import { StatusTitle, StatusType } from "./constants.js";
3+
4+
const statuses: Status[] = [
5+
{
6+
type: StatusType.TODO,
7+
title: StatusTitle.TODO,
8+
},
9+
{
10+
type: StatusType.IN_PROGRESS,
11+
title: StatusTitle.IN_PROGRESS,
12+
},
13+
{
14+
type: StatusType.DONE,
15+
title: StatusTitle.DONE,
16+
},
17+
];
18+
19+
let tasks: Task[] = loadTasks();
20+
21+
const app = document.getElementById("app")!;
22+
23+
function saveTasks() {
24+
localStorage.setItem("trello-tasks", JSON.stringify(tasks));
25+
}
26+
27+
function loadTasks(): Task[] {
28+
const saved = localStorage.getItem("trello-tasks");
29+
return saved ? JSON.parse(saved) : [];
30+
}
31+
32+
function renderBoard() {
33+
app.innerHTML = "";
34+
const board = document.createElement("div");
35+
board.className = "board";
36+
37+
for (const status of statuses) {
38+
const column = document.createElement("div");
39+
column.className = "column";
40+
column.style.setProperty(
41+
"--column-color",
42+
`var(--${status.type.toLowerCase().trim().replace(/_/g, "-")}-color)`
43+
);
44+
column.dataset.status = status.type;
45+
46+
const header = document.createElement("h2");
47+
header.innerText = status.title;
48+
column.appendChild(header);
49+
50+
const statusTasks = tasks.filter((t) => t.status.type === status.type);
51+
52+
for (const task of statusTasks) {
53+
const taskDiv = createTaskElement(task);
54+
column.appendChild(taskDiv);
55+
}
56+
57+
const form = document.createElement("form");
58+
form.className = "add-task-form";
59+
const input = document.createElement("input");
60+
input.placeholder = "New task";
61+
const addBtn = document.createElement("button");
62+
addBtn.type = "submit";
63+
addBtn.textContent = "+";
64+
65+
form.appendChild(input);
66+
form.appendChild(addBtn);
67+
form.onsubmit = (e) => {
68+
e.preventDefault();
69+
if (input.value.trim()) {
70+
addTask(input.value.trim(), status);
71+
input.value = "";
72+
}
73+
};
74+
column.appendChild(form);
75+
76+
column.addEventListener("dragover", (e) => {
77+
e.preventDefault();
78+
});
79+
80+
column.addEventListener("drop", (e) => {
81+
const taskId = e.dataTransfer?.getData("text/plain");
82+
if (taskId) {
83+
const task = tasks.find((t) => t.id === taskId);
84+
if (task) {
85+
task.status = status;
86+
saveTasks();
87+
renderBoard();
88+
}
89+
}
90+
});
91+
92+
board.appendChild(column);
93+
}
94+
95+
app.appendChild(board);
96+
}
97+
98+
function createTaskElement(task: Task): HTMLDivElement {
99+
const div = document.createElement("div");
100+
div.className = "task";
101+
div.draggable = true;
102+
div.dataset.id = task.id;
103+
104+
const input = document.createElement("input");
105+
input.value = task.title;
106+
input.onchange = () => updateTask(task.id, input.value);
107+
108+
const delBtn = document.createElement("button");
109+
delBtn.textContent = "🗑";
110+
delBtn.onclick = () => deleteTask(task.id);
111+
112+
div.appendChild(input);
113+
div.appendChild(delBtn);
114+
115+
div.addEventListener("dragstart", (e) => {
116+
e.dataTransfer?.setData("text/plain", task.id);
117+
});
118+
119+
return div;
120+
}
121+
122+
function addTask(title: string, status: Status) {
123+
tasks.push({ id: crypto.randomUUID(), title, status });
124+
saveTasks();
125+
renderBoard();
126+
}
127+
128+
function updateTask(id: string, newTitle: string) {
129+
const task = tasks.find((t) => t.id === id);
130+
if (task) {
131+
task.title = newTitle;
132+
saveTasks();
133+
renderBoard();
134+
}
135+
}
136+
137+
function deleteTask(id: string) {
138+
tasks = tasks.filter((t) => t.id !== id);
139+
saveTasks();
140+
renderBoard();
141+
}
142+
143+
renderBoard();

src/constants.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export const StatusType = {
2+
TODO: "TODO",
3+
IN_PROGRESS: "IN_PROGRESS",
4+
DONE: "DONE",
5+
} as const;
6+
7+
export const StatusTitle = {
8+
TODO: "To Do",
9+
IN_PROGRESS: "In Progress",
10+
DONE: "Done",
11+
} as const satisfies Record<keyof typeof StatusType, string>;

src/index.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<title>Task Board</title>
6+
<link rel="stylesheet" href="./styles.css" />
7+
</head>
8+
<body>
9+
<div id="app"></div>
10+
<script type="module" src="../dist/index.js"></script>
11+
</body>
12+
</html>

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import "./app.js";

src/styles.css

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
:root {
2+
--backgroud-color: #f4f4f4;
3+
--column-background-color: white;
4+
--card-color: #e2e2e2;
5+
--text-color: black;
6+
--add-tast-input-color: #eee;
7+
8+
--delete-icon-color: red;
9+
10+
--todo-color: red;
11+
--in-progress-color: orange;
12+
--done-color: green;
13+
}
14+
15+
@media screen and (prefers-color-scheme: dark) {
16+
:root {
17+
--backgroud-color: #111111;
18+
--column-background-color: black;
19+
--card-color: #222222;
20+
--text-color: white;
21+
--add-tast-input-color: #222;
22+
23+
--delete-icon-color: #ff000044;
24+
25+
--todo-color: red;
26+
--in-progress-color: orange;
27+
--done-color: green;
28+
}
29+
}
30+
31+
* {
32+
box-sizing: border-box;
33+
}
34+
35+
body {
36+
font-family: sans-serif;
37+
margin: 0;
38+
padding: 0;
39+
background: var(--backgroud-color);
40+
41+
color: var(--text-color);
42+
}
43+
44+
#app {
45+
height: 100dvh;
46+
}
47+
48+
.board {
49+
display: flex;
50+
gap: 16px;
51+
padding: 20px;
52+
height: 100%;
53+
}
54+
55+
.column {
56+
background: var(--column-background-color);
57+
border-radius: 1rem;
58+
padding: 1rem;
59+
min-width: 300px;
60+
flex: 1;
61+
62+
display: flex;
63+
flex-direction: column;
64+
}
65+
66+
.column h2 {
67+
margin-top: 0;
68+
color: var(--column-color);
69+
}
70+
71+
.task {
72+
padding: 1rem;
73+
margin-bottom: 0.75rem;
74+
background: var(--card-color);
75+
border-radius: 4px;
76+
cursor: grab;
77+
78+
display: flex;
79+
align-items: center;
80+
}
81+
82+
.task input {
83+
background: transparent;
84+
color: var(--text-color);
85+
margin: 0;
86+
border: none;
87+
88+
width: 100%;
89+
margin-bottom: 5px;
90+
}
91+
92+
.task button {
93+
height: 2rem;
94+
width: 2rem;
95+
96+
border-radius: 50%;
97+
flex-shrink: 0;
98+
border: none;
99+
opacity: 0;
100+
101+
cursor: pointer;
102+
103+
background-color: var(--delete-icon-color);
104+
}
105+
106+
.task:hover button,
107+
.task button:focus {
108+
opacity: 1;
109+
}
110+
111+
.add-task-form {
112+
margin-top: auto;
113+
width: 100%;
114+
display: flex;
115+
align-items: center;
116+
gap: 0.5rem;
117+
}
118+
119+
.add-task-form input {
120+
flex: 1;
121+
background: var(--add-tast-input-color);
122+
color: var(--text-color);
123+
border: none;
124+
border-radius: 2rem;
125+
height: 2rem;
126+
padding: 0 0.5rem;
127+
}
128+
.add-task-form button {
129+
flex-shrink: 0;
130+
color: var(--text-color);
131+
background: var(--card-color);
132+
border-radius: 50%;
133+
border: none;
134+
height: 2rem;
135+
width: 2rem;
136+
}

src/types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { StatusType, StatusTitle } from "./constants.js";
2+
3+
export type Status = {
4+
type: (typeof StatusType)[keyof typeof StatusType];
5+
title: (typeof StatusTitle)[keyof typeof StatusTitle];
6+
};
7+
8+
export type Task = {
9+
id: string;
10+
title: string;
11+
status: Status;
12+
};

tsconfig.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"compilerOptions": {
3+
"module": "ESNext",
4+
"target": "ES2020",
5+
"moduleResolution": "bundler",
6+
"outDir": "dist",
7+
"rootDir": "src",
8+
"resolveJsonModule": true,
9+
"esModuleInterop": true,
10+
"allowSyntheticDefaultImports": true,
11+
"forceConsistentCasingInFileNames": true,
12+
"strict": true,
13+
"skipLibCheck": true
14+
},
15+
"include": ["src"]
16+
}

0 commit comments

Comments
 (0)