Skip to content

Commit 15be811

Browse files
committed
feat: implement basic semi-interactive task board
1 parent ab5b67f commit 15be811

23 files changed

+6540
-0
lines changed

.babelrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"presets": ["@babel/preset-env", "@babel/preset-react"]
3+
}

.github/workflows/deploy.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Deploy to GitHub Pages
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
9+
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
10+
concurrency:
11+
group: "pages"
12+
cancel-in-progress: false
13+
14+
jobs:
15+
buildAndDeploy:
16+
runs-on: ubuntu-latest
17+
18+
name: Build and deploy
19+
20+
steps:
21+
- name: Checkout code
22+
uses: actions/checkout@v3
23+
24+
- name: Install dependencies
25+
run: yarn
26+
27+
- name: Set up Git user config
28+
run: |
29+
git config user.email "[email protected]"
30+
git config user.name "GitHub Actions"
31+
32+
# TODO: Check if it can be solved otherwise
33+
# Maybe it's because the SSH connection (try to use github-personal.com instead of github.com)
34+
- name: Set Remote URL with token
35+
run: git remote set-url origin "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}"
36+
37+
- name: Build and deploy project
38+
run: yarn deploy
39+
# env:
40+
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

jest.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// jest.config.js
2+
module.exports = {
3+
preset: "ts-jest",
4+
testEnvironment: "node",
5+
testMatch: ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[tj]s?(x)"],
6+
};

package.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"name": "rinczefi-cv",
3+
"version": "1.0.0",
4+
"description": "My biography written in React.js (TS)",
5+
"main": "index.js",
6+
"scripts": {
7+
"start": "webpack serve --mode development",
8+
"build": "webpack --mode production",
9+
"test": "jest",
10+
"predeploy": "yarn build",
11+
"deploy": "gh-pages -d dist"
12+
},
13+
"author": "Inczefi Robert",
14+
"license": "MIT",
15+
"dependencies": {
16+
"@emotion/react": "11.13.3",
17+
"@emotion/styled": "11.13.0",
18+
"@hello-pangea/dnd": "16.6.0",
19+
"@mui/material": "6.0.1",
20+
"react": "18.3.1",
21+
"react-dom": "18.3.1",
22+
"typescript": "5.5.4"
23+
},
24+
"devDependencies": {
25+
"@babel/core": "7.25.2",
26+
"@babel/preset-env": "7.25.4",
27+
"@babel/preset-react": "7.24.7",
28+
"@types/jest": "29.5.12",
29+
"@types/react": "18.3.5",
30+
"@types/react-dom": "18.3.0",
31+
"babel-loader": "9.1.3",
32+
"css-loader": "7.1.2",
33+
"gh-pages": "6.1.1",
34+
"html-webpack-plugin": "5.6.0",
35+
"jest": "29.7.0",
36+
"style-loader": "4.0.0",
37+
"ts-jest": "29.2.5",
38+
"ts-loader": "9.5.1",
39+
"webpack": "5.94.0",
40+
"webpack-cli": "5.1.4",
41+
"webpack-dev-server": "5.0.4"
42+
}
43+
}

public/index.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Inczefi Robert CV</title>
7+
</head>
8+
<body>
9+
<div id="root"></div>
10+
</body>
11+
</html>

src/App.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import React from "react";
2+
import ScrumBoard from "./components/ScrumBoard";
3+
4+
const App: React.FC = () => {
5+
return <ScrumBoard />;
6+
};
7+
8+
export default App;

src/components/Column.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// src/components/Column.tsx
2+
import React from "react";
3+
import Paper from "@mui/material/Paper";
4+
import Typography from "@mui/material/Typography";
5+
import { Droppable } from "@hello-pangea/dnd";
6+
import TaskCard from "./TaskCard";
7+
import type { Task } from "../types";
8+
9+
interface Props {
10+
title: string;
11+
tasks: Task[];
12+
columnId: string;
13+
}
14+
15+
const Column: React.FC<Props> = ({ title, tasks, columnId }) => {
16+
return (
17+
<Paper sx={{ width: 300, padding: 2, margin: 1 }}>
18+
<Typography variant="h5" align="center" gutterBottom>
19+
{title}
20+
</Typography>
21+
<Droppable droppableId={columnId}>
22+
{(provided) => (
23+
<div
24+
{...provided.droppableProps}
25+
ref={provided.innerRef}
26+
style={{ minHeight: 100 }}
27+
>
28+
{tasks.map((task, index) => (
29+
<TaskCard key={task.id} task={task} index={index} />
30+
))}
31+
{provided.placeholder}
32+
</div>
33+
)}
34+
</Droppable>
35+
</Paper>
36+
);
37+
};
38+
39+
export default Column;

src/components/ScrumBoard.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// src/components/ScrumBoard.tsx
2+
import React, { useState } from "react";
3+
import Box from "@mui/material/Box";
4+
import Grid from "@mui/material/Grid2";
5+
import { DragDropContext, type DropResult } from "@hello-pangea/dnd";
6+
import Column from "./Column";
7+
import { type Task, TaskStatus } from "../types";
8+
import { initialTasks } from "../data/tasks";
9+
import { getTasksByStatus, getUpdatedTasks } from "../helpers";
10+
11+
const ScrumBoard: React.FC = () => {
12+
const [tasks, setTasks] = useState<Task[]>(initialTasks);
13+
14+
const onDragEnd = (result: DropResult) => {
15+
const updatedTasks = getUpdatedTasks(tasks, result);
16+
17+
if (updatedTasks) {
18+
setTasks(updatedTasks);
19+
}
20+
};
21+
22+
return (
23+
<DragDropContext onDragEnd={onDragEnd}>
24+
<Box sx={{ flexGrow: 1, padding: 2 }}>
25+
<Grid container spacing={2} justifyContent="center">
26+
<Column
27+
title={TaskStatus.ToDo}
28+
tasks={getTasksByStatus(tasks, TaskStatus.ToDo)}
29+
columnId={TaskStatus.ToDo}
30+
/>
31+
<Column
32+
title={TaskStatus.InProgress}
33+
tasks={getTasksByStatus(tasks, TaskStatus.InProgress)}
34+
columnId={TaskStatus.InProgress}
35+
/>
36+
<Column
37+
title={TaskStatus.Done}
38+
tasks={getTasksByStatus(tasks, TaskStatus.Done)}
39+
columnId={TaskStatus.Done}
40+
/>
41+
</Grid>
42+
</Box>
43+
</DragDropContext>
44+
);
45+
};
46+
47+
export default ScrumBoard;

src/components/TaskCard.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// src/components/TaskCard.tsx
2+
import React from "react";
3+
import Card from "@mui/material/Card";
4+
import CardContent from "@mui/material/CardContent";
5+
import Typography from "@mui/material/Typography";
6+
import { Draggable } from "@hello-pangea/dnd";
7+
import type { Task } from "../types";
8+
9+
interface Props {
10+
task: Task;
11+
index: number; // Required for draggable item
12+
}
13+
14+
const TaskCard: React.FC<Props> = ({ task, index }) => {
15+
return (
16+
<Draggable draggableId={task.id.toString()} index={index}>
17+
{(provided) => (
18+
<div
19+
ref={provided.innerRef}
20+
{...provided.draggableProps}
21+
{...provided.dragHandleProps}
22+
style={{ marginBottom: "8px", ...provided.draggableProps.style }}
23+
>
24+
<Card>
25+
<CardContent>
26+
<Typography variant="h6" component="div">
27+
{task.title}
28+
</Typography>
29+
<Typography variant="body2" color="text.secondary">
30+
{task.description}
31+
</Typography>
32+
</CardContent>
33+
</Card>
34+
</div>
35+
)}
36+
</Draggable>
37+
);
38+
};
39+
40+
export default TaskCard;

src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as ScrumBoard } from "./ScrumBoard";

src/data/tasks.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { type Task, TaskStatus } from "../types";
2+
3+
export const initialTasks: Task[] = [
4+
{
5+
id: 1,
6+
title: "Task 1",
7+
description: "Description for Task 1",
8+
status: TaskStatus.ToDo,
9+
},
10+
{
11+
id: 2,
12+
title: "Task 2",
13+
description: "Description for Task 2",
14+
status: TaskStatus.ToDo,
15+
},
16+
{
17+
id: 3,
18+
title: "Task 3",
19+
description: "Description for Task 3",
20+
status: TaskStatus.InProgress,
21+
},
22+
{
23+
id: 4,
24+
title: "Task 4",
25+
description: "Description for Task 4",
26+
status: TaskStatus.InProgress,
27+
},
28+
{
29+
id: 5,
30+
title: "Task 5",
31+
description: "Description for Task 5",
32+
status: TaskStatus.InProgress,
33+
},
34+
{
35+
id: 6,
36+
title: "Task 6",
37+
description: "Description for Task 6",
38+
status: TaskStatus.Done,
39+
},
40+
{
41+
id: 7,
42+
title: "Task 7",
43+
description: "Description for Task 7",
44+
status: TaskStatus.Done,
45+
},
46+
];
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import getTasksByStatus from "../getTasksByStatus";
2+
import { type Task, TaskStatus } from "../../types";
3+
import {
4+
doneTask,
5+
firstToDoTask,
6+
inProgressTask,
7+
secondToDoTask,
8+
} from "../mocks/tasks";
9+
10+
describe("getTasksByStatus", () => {
11+
const initialTasks: Task[] = [
12+
firstToDoTask,
13+
secondToDoTask,
14+
inProgressTask,
15+
doneTask,
16+
];
17+
18+
it("should return tasks with status 'To Do'", () => {
19+
const result = getTasksByStatus(initialTasks, TaskStatus.ToDo);
20+
expect(result).toEqual([firstToDoTask, secondToDoTask]);
21+
});
22+
23+
it("should return tasks with status 'In Progress'", () => {
24+
const result = getTasksByStatus(initialTasks, TaskStatus.InProgress);
25+
expect(result).toEqual([inProgressTask]);
26+
});
27+
28+
it("should return tasks with status 'Done'", () => {
29+
const result = getTasksByStatus(initialTasks, TaskStatus.Done);
30+
expect(result).toEqual([doneTask]);
31+
});
32+
33+
it("should return an empty array if no tasks match the status", () => {
34+
const result = getTasksByStatus(
35+
initialTasks,
36+
"Invalid Status" as Task["status"]
37+
);
38+
expect(result).toEqual([]);
39+
});
40+
});

0 commit comments

Comments
 (0)