0% found this document useful (0 votes)
3 views8 pages

Example

This document is a React application for a Kanban board that allows users to manage tasks with different statuses (To Do, On Progress, Done, Archived). It utilizes drag-and-drop functionality for task management and includes forms for adding new tasks, PICs, and activities, with data fetched from a backend API. The application also handles state management for tasks and modal controls for user interactions.

Uploaded by

Ramadhani
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views8 pages

Example

This document is a React application for a Kanban board that allows users to manage tasks with different statuses (To Do, On Progress, Done, Archived). It utilizes drag-and-drop functionality for task management and includes forms for adding new tasks, PICs, and activities, with data fetched from a backend API. The application also handles state management for tasks and modal controls for user interactions.

Uploaded by

Ramadhani
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 8

import React, { useState, useEffect } from 'react';

import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';


import axios from 'axios';

const statuses = [
{ id: 'todo', title: 'To Do', color: '#FF6347' },
{ id: 'progress', title: 'On Progress', color: '#FFA500' },
{ id: 'done', title: 'Done', color: '#32CD32' },
{ id: 'archived', title: 'Archived', color: '#A9A9A9' },
];

const API_BASE_URL = 'http://localhost:3000'; // Ganti sesuai backend Anda

function App() {
// Data state
const [tasks, setTasks] = useState([]);
const [pics, setPics] = useState([]);
const [taskOptions, setTaskOptions] = useState([]);

// Modal control state


const [modalOpen, setModalOpen] = useState(null); // 'task' | 'pic' | 'activity'
| null

// Form states
const [newTaskContent, setNewTaskContent] = useState('');
const [newTaskPic, setNewTaskPic] = useState('');
const [newTaskStatus, setNewTaskStatus] = useState('todo');
const [newTaskDetail, setNewTaskDetail] = useState('');

const [newPicName, setNewPicName] = useState('');


const [newActivityName, setNewActivityName] = useState('');

// Load initial data


useEffect(() => {
fetchData();
}, []);

const fetchData = () => {


axios.get(`${API_BASE_URL}/api/tasks`).then((res) => setTasks(res.data));
axios.get(`${API_BASE_URL}/api/pics`).then((res) => setPics(res.data));
axios.get(`${API_BASE_URL}/api/task-options`).then((res) =>
setTaskOptions(res.data));
};

// Update task status and timestamps on drag end


const onDragEnd = (result) => {
const { destination, source, draggableId } = result;
if (!destination) return;
if (
destination.droppableId === source.droppableId &&
destination.index === source.index
)
return;

const task = tasks.find((t) => t.id === draggableId);


if (!task) return;

const newStatus = destination.droppableId;


const now = new Date().toISOString();
const newTimestamps = { ...task.timestamps };
newTimestamps[newStatus] = now;

const updatedTask = {
...task,
status: newStatus,
timestamps: newTimestamps,
};

const newTasks = tasks.map((t) => (t.id === task.id ? updatedTask : t));


setTasks(newTasks);

axios
.post(`${API_BASE_URL}/api/tasks/update`, updatedTask)
.catch((err) => console.error('Error updating task:', err));
};

// Handlers for adding new data


const handleAddTask = async () => {
if (!newTaskContent || !newTaskPic) {
alert('Please select Task and PIC');
return;
}
const now = new Date().toISOString();
const newTask = {
content: newTaskContent,
pic: newTaskPic,
detail: newTaskDetail,
status: newTaskStatus,
timestamps: {
todo: null,
progress: null,
done: null,
archived: null,
},
};
newTask.timestamps[newTaskStatus] = now;

try {
await axios.post(`${API_BASE_URL}/api/tasks`, newTask);
fetchData();
closeModal();
resetTaskForm();
} catch (error) {
alert('Failed to add task');
console.error(error);
}
};

const handleAddPic = async () => {


if (!newPicName.trim()) {
alert('Please enter PIC name');
return;
}
try {
await axios.post(`${API_BASE_URL}/api/pics`, { name: newPicName.trim() });
fetchData();
closeModal();
setNewPicName('');
} catch (error) {
alert('Failed to add PIC');
console.error(error);
}
};

const handleAddActivity = async () => {


if (!newActivityName.trim()) {
alert('Please enter Activity name');
return;
}
try {
await axios.post(`${API_BASE_URL}/api/task-options`, { name:
newActivityName.trim() });
fetchData();
closeModal();
setNewActivityName('');
} catch (error) {
alert('Failed to add Activity');
console.error(error);
}
};

const closeModal = () => setModalOpen(null);

const resetTaskForm = () => {


setNewTaskContent('');
setNewTaskPic('');
setNewTaskStatus('todo');
setNewTaskDetail('');
};

// Modal component
const Modal = ({ children }) => (
<div
style={{
position: 'fixed',
top: 0, left: 0, right: 0, bottom: 0,
backgroundColor: 'rgba(0,0,0,0.5)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 1000,
}}
onClick={closeModal}
>
<div
style={{
backgroundColor: 'white',
padding: 20,
borderRadius: 8,
minWidth: 320,
maxWidth: '90%',
boxShadow: '0 2px 10px rgba(0,0,0,0.3)',
}}
onClick={(e) => e.stopPropagation()}
>
{children}
</div>
</div>
);

return (
<div>
{/* Navbar */}
<nav
style={{
backgroundColor: '#333',
color: 'white',
padding: '10px 20px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<div style={{ fontWeight: 'bold', fontSize: 20 }}>Kanban App</div>
<div>
<button
style={navButtonStyle}
onClick={() => setModalOpen('task')}
>
Add Task
</button>
<button
style={navButtonStyle}
onClick={() => setModalOpen('pic')}
>
Add PIC
</button>
<button
style={navButtonStyle}
onClick={() => setModalOpen('activity')}
>
Add Activity
</button>
</div>
</nav>

{/* Kanban Board */}


<DragDropContext onDragEnd={onDragEnd}>
<div style={{ display: 'flex', padding: 20, gap: 20 }}>
{statuses.map(({ id, title, color }) => (
<Droppable droppableId={id} key={id}>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.droppableProps}
style={{
backgroundColor: '#f0f0f0',
padding: 10,
borderRadius: 5,
width: 280,
minHeight: 500,
}}
>
<h3 style={{ color }}>{title}</h3>
{tasks
.filter((task) => task.status === id)
.map((task, index) => (
<Draggable
draggableId={task.id}
index={index}
key={task.id}
>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
userSelect: 'none',
padding: 16,
margin: '0 0 8px 0',
minHeight: '50px',
backgroundColor: color,
color: 'white',
borderRadius: 4,
boxShadow: snapshot.isDragging
? '0 2px 8px rgba(0,0,0,0.2)'
: 'none',
...provided.draggableProps.style,
}}
>
<div><strong>{task.content}</strong></div>
<div>PIC: {task.pic}</div>
<div>Detail: {task.detail}</div>
<small>
Timestamps:
<ul style={{ paddingLeft: 15, margin: 0 }}>
{Object.entries(task.timestamps).map(
([statusKey, time]) => (
<li key={statusKey}>
{statuses.find((s) => s.id ===
statusKey)?.title} :{' '}
{time ? new Date(time).toLocaleString() :
'-'}
</li>
)
)}
</ul>
</small>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
))}
</div>
</DragDropContext>

{/* Modal Forms */}


{modalOpen === 'task' && (
<Modal>
<h3>Add New Task</h3>
<div style={{ marginBottom: 10 }}>
<label>
Task:{' '}
<select
value={newTaskContent}
onChange={(e) => setNewTaskContent(e.target.value)}
>
<option value="">-- Select Task --</option>
{taskOptions.map((opt) => (
<option key={opt} value={opt}>
{opt}
</option>
))}
</select>
</label>
</div>
<div style={{ marginBottom: 10 }}>
<label>
PIC:{' '}
<select
value={newTaskPic}
onChange={(e) => setNewTaskPic(e.target.value)}
>
<option value="">-- Select PIC --</option>
{pics.map((pic) => (
<option key={pic} value={pic}>
{pic}
</option>
))}
</select>
</label>
</div>
<div style={{ marginBottom: 10 }}>
<label>
Status:{' '}
<select
value={newTaskStatus}
onChange={(e) => setNewTaskStatus(e.target.value)}
>
{statuses.map(({ id, title }) => (
<option key={id} value={id}>
{title}
</option>
))}
</select>
</label>
</div>
<div style={{ marginBottom: 10 }}>
<label>
Detail:{' '}
<input
type="text"
value={newTaskDetail}
onChange={(e) => setNewTaskDetail(e.target.value)}
placeholder="Task detail"
style={{ width: '100%' }}
/>
</label>
</div>
<div style={{ textAlign: 'right' }}>
<button onClick={closeModal} style={{ marginRight: 10 }}>
Cancel
</button>
<button onClick={handleAddTask}>Add Task</button>
</div>
</Modal>
)}

{modalOpen === 'pic' && (


<Modal>
<h3>Add New PIC</h3>
<div style={{ marginBottom: 10 }}>
<label>
PIC Name:{' '}
<input
type="text"
value={newPicName}
onChange={(e) => setNewPicName(e.target.value)}
placeholder="Enter PIC name"
style={{ width: '100%' }}
/>
</label>
</div>
<div style={{ textAlign: 'right' }}>
<button onClick={closeModal} style={{ marginRight: 10 }}>
Cancel
</button>
<button onClick={handleAddPic}>Add PIC</button>
</div>
</Modal>
)}

{modalOpen === 'activity' && (


<Modal>
<h3>Add New Activity</h3>
<div style={{ marginBottom: 10 }}>
<label>
Activity Name:{' '}
<input
type="text"
value={newActivityName}
onChange={(e) => setNewActivityName(e.target.value)}
placeholder="Enter activity name"
style={{ width: '100%' }}
/>
</label>
</div>
<div style={{ textAlign: 'right' }}>
<button onClick={closeModal} style={{ marginRight: 10 }}>
Cancel
</button>
<button onClick={handleAddActivity}>Add Activity</button>
</div>
</Modal>
)}
</div>
);
}

const navButtonStyle = {
backgroundColor: '#555',
color: 'white',
border: 'none',
padding: '8px 12px',
marginLeft: 10,
borderRadius: 4,
cursor: 'pointer',
};

export default App;

You might also like