Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c7560af
create task components and a store for task
Jan 12, 2026
401ef53
create form component and add functionality for open it/close as dialog
Jan 13, 2026
3d14d2c
add functionality to add a new task through the form
Jan 13, 2026
84d8d47
add day.js for date creation
Jan 13, 2026
6e60ebe
add store updater function for task completion status that updates on…
Jan 13, 2026
2235a3c
update the data in the task
Jan 13, 2026
551a0d7
add some styling to task container and task status button to indicate…
Jan 13, 2026
3ac3649
add styling to add task button
Jan 13, 2026
f6e539a
add a page component for the task overview + minor styling
Jan 13, 2026
1fc8267
add functionality to remove a task
Jan 14, 2026
c9e2de2
separate tasks that are completed from not completed in task container
Jan 14, 2026
ee193d3
add logo
Jan 14, 2026
7720e0f
add persist for saving the tasks in local storage
Jan 14, 2026
fcb44d7
add components for empty states
Jan 14, 2026
835334c
remove dummy data
Jan 14, 2026
bbe5284
add task category component + some styling of task components
Jan 14, 2026
cc04b8b
add accessibility improvements on buttons
Jan 14, 2026
b8c265b
accessibility fix
Jan 14, 2026
d8e0b4c
add chart and task summary components + styling
Jan 14, 2026
c8c8ed4
more styling to chart and task summary
Jan 15, 2026
f8d9fb8
styling of form
Jan 15, 2026
ba7011e
add task deadline component and make text go red if overdue
Jan 15, 2026
61df55d
remove deadline from task if completed
Jan 15, 2026
ed6ce50
styling + adding more category options in form
Jan 16, 2026
ee89fef
styling changes
Jan 16, 2026
c33f007
clean code
Jan 16, 2026
5c9a578
update readme
Jan 16, 2026
2a964f3
add color for deadline in theme + clean code
Feb 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
# Todo
# To-do App
A task management application built with React, focusing on state management and a clean, component-driven architecture. The app uses Zustand for global state handling, enabling centralized task creation, updates, and persistence. Styled Components are used for scoped styling, ensuring a consistent and maintainable UI.

# Netlify link:
https://planiture.netlify.app/

# Tech
- JavaScript
- React
- Global State Management / Zustand
- Styled components
- CSS + HTML
4 changes: 2 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
<link rel="icon" type="image/svg+xml" href="./logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Todo</title>
<title>Planiture - Your planning app</title>
</head>
<body>
<div id="root"></div>
Expand Down
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,19 @@
"preview": "vite preview"
},
"dependencies": {
"dayjs": "^1.11.19",
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react-dom": "^19.0.0",
"recharts": "^3.6.0",
"styled-components": "^6.3.5",
"zustand": "^5.0.10"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"@vitejs/plugin-react": "^4.7.0",
"babel-plugin-styled-components": "^2.1.4",
"eslint": "^9.21.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
Expand Down
Binary file added public/logo-white.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion public/vite.svg

This file was deleted.

13 changes: 12 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import { ThemeProvider } from 'styled-components';
import { theme } from './styles/Theme.styled';
import { GlobalStyle } from './styles/GlobalStyles';
import { TaskOverviewPage } from './pages/TaskOverviewPage';


export const App = () => {
return (
<h1>React Boilerplate</h1>
<>
<ThemeProvider theme={theme}>
<GlobalStyle />
<TaskOverviewPage />
</ThemeProvider>
</>
)
}
Binary file added src/assets/bw-confetti.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/bwflags.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/confetti-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/confetti-gray.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/confetti.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/flags.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/label.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/man-on-the-moon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/rocket.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/tag.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 43 additions & 0 deletions src/components/Chart.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useTaskStore } from "../stores/taskStore";
import { PieChart, Pie, Cell, Legend } from "recharts";

export const Chart = () => {

const tasks = useTaskStore(state => state.tasks);

const categories = tasks.map((task)=> task.category);

//count how many instances of each category
const categoryCount = categories.reduce((acc, category) => {
acc[category] = (acc[category]||0) + 1;
return acc;
}, {});

//turn into an object array to use for pie chart
const pieChartCategoryData = Object.entries(categoryCount).map(([name, value])=>({ name, value }));

const sliceColors = ["#52C5FF", "#14B8A6", "#2563EB", "#4F46E5", "#A78BFA", "#9a64c2", "#a8459f", "#ad2077", "#7C3AED"];

return(
<PieChart width={290} height={180}>
<Pie
data={pieChartCategoryData}
dataKey="value"
nameKey="name"
cx="40%" //make space for legend
cy="50%"
outerRadius={60}
>
{pieChartCategoryData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={sliceColors[index % sliceColors.length]} />
))}
</Pie>
<Legend
verticalAlign="middle"
align="right"
layout="vertical"
iconType="circle"
/>
</PieChart>
);
}
41 changes: 41 additions & 0 deletions src/components/EmptyState.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import styled from "styled-components";
import moon from "../assets/man-on-the-moon.png";
import rocket from "../assets/rocket.gif";


export const EmptyTasksMessage = () => {
return(
<StyledWrapper>
<p><b>Things to do? Let us help you get organized!</b><br></br>Add your first task to get started.</p>
<StyledAnimation src={rocket} alt="Rocket animation" />
</StyledWrapper>
);
}

export const EmptyIncompleteTasksMessage = () => {
return(
<StyledWrapper>
<StyledImg src={moon} alt="confetti"/>
<p><b>Hurray, there's nothing left to do!</b><br></br>All tasks are completed.</p>
</StyledWrapper>
);
}

const StyledImg = styled.img`
width: auto;
height: 82px;
`;

const StyledAnimation = styled.img`
width: auto;
height: 100px;
`;

const StyledWrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
color: ${props => props.theme.colors.main.secondaryText};
margin: 42px;
`;
64 changes: 64 additions & 0 deletions src/components/Task.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { TaskStatusBtn } from "./buttons/TaskStatusBtn";
import { RemoveTaskBtn } from "./buttons/RemoveTaskBtn";
import { useTaskStore } from "../stores/taskStore";
import { TaskCategory } from "./TaskCategory";
import { TaskDeadline } from "./TaskDeadline";
import styled from "styled-components";

export const Task = ({ task }) => {

const updateCompletionStatus = useTaskStore(state => state.updateCompletionStatus);

const removeTask = useTaskStore(state => state.removeTask);

return(
<StyledDiv>
<StyledOuterWrapper>
<StyledTaskHeader>
{task.category && <TaskCategory>{task.category}</TaskCategory>}
<RemoveTaskBtn onClick={() => removeTask(task.id)} />
</StyledTaskHeader>
<StyledInnerWrapper>
<div>
<h3>{task.name}</h3>
{task.description && <p>{task.description}</p>}
{task.deadline && task.completed === false && <TaskDeadline deadline={task.deadline}>{task.deadline}</TaskDeadline>}
</div>
<TaskStatusBtn onClick={() => updateCompletionStatus(task.id, task.completed)} completed={task.completed}/>
</StyledInnerWrapper>
</StyledOuterWrapper>
</StyledDiv>
);
}

const StyledDiv = styled.div`
width: 100%;
padding: 12px 24px;
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.06);
display: flex;
justify-content: space-between;
align-items: center;
min-width: 40vw;
border-radius: 12px;
`;

const StyledOuterWrapper = styled.div`
display: flex;
flex-direction: column;
width: 100%;
margin-bottom: 12px;
`;

const StyledInnerWrapper = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
margin: 12px 0;
`;

const StyledTaskHeader = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
`;
27 changes: 27 additions & 0 deletions src/components/TaskCategory.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import label from "../assets/label.png";
import styled from "styled-components";

export const TaskCategory = ({ children }) => {
return(
<StyledWrapper>
<StyledIcon src={label} alt="label" />
<StyledParagraph>{children}</StyledParagraph>
</StyledWrapper>
);
}

const StyledWrapper = styled.div`
display: flex;
align-items: center;
color: ${props => props.theme.colors.main.secondaryText};
margin: 0;
`;
const StyledIcon = styled.img`
width: auto;
height: 18px;
margin-right: 8px;
`;
const StyledParagraph = styled.p`
margin: 0;
`;

32 changes: 32 additions & 0 deletions src/components/TaskContainer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useTaskStore } from "../stores/taskStore";
import { Task } from "./Task";
import { AddTaskBtn } from "../components/buttons/AddTaskBtn";
import { EmptyTasksMessage, EmptyIncompleteTasksMessage } from "./EmptyState";
import styled from "styled-components";

export const TaskContainer = ({ openForm }) => {

const tasks = useTaskStore(state => state.tasks);
const incompletedTasks = tasks?.filter(task => task.completed === false);
const completedTasks = tasks?.filter(task => task.completed);

return(
<StyledDiv>
{(!tasks || tasks.length < 1) && <EmptyTasksMessage />}
{tasks && incompletedTasks.length > 0 && <h2>To-do list</h2>}
{tasks && tasks.length > 0 && incompletedTasks.length < 1 && <EmptyIncompleteTasksMessage />}
{tasks && incompletedTasks.map(task => (<Task key={task.id} task={task} />))}
{tasks && tasks.length > 0 && <AddTaskBtn onClick={openForm}/>}
{completedTasks && completedTasks.length > 0 && <h2>Completed tasks</h2>}
{completedTasks.map(task => (<Task key={task.id} task={task} />))}
</StyledDiv>
);
}

const StyledDiv = styled.div`
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
margin: 12px 16px 24px 16px;
`;
17 changes: 17 additions & 0 deletions src/components/TaskDeadline.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import styled from "styled-components";
import dayjs from "dayjs";

export const TaskDeadline = ({ deadline, children }) => {

const isOverDue = dayjs().isAfter(dayjs(deadline), "day");

return(
<>
<StyledParagraph $isOverDue={isOverDue}><i>Deadline: {children}</i></StyledParagraph>
</>
);
}

const StyledParagraph = styled.p`
color: ${props => props.$isOverDue ? props => props.theme.colors.button.overdue : props => props.theme.colors.main.secondaryText};
`;
Loading