const { projectId, contract, componentOwnerId, addActionStatus } = props; const AddColumnContainer = styled.div` display: flex; flex-direction: column; align-items: center; `; const HorizontalFlexContainer = styled.div` display: flex; flex-direction: row; overflow-x: scroll; overflow-y: scroll; height: 100%; width: 100%; scroll-behavior: smooth; scrollbar-color: black black; ::-webkit-scrollbar { width: 6px; } ::-webkit-scrollbar-thumb { background-color: black; border-radius: 6px; } ::-webkit-scrollbar-thumb:hover { background-color: black; } * { scrollbar-color: black black; } html::-webkit-scrollbar { width: 12px; } html::-webkit-scrollbar-thumb { background-color: black; border-radius: 6px; } html::-webkit-scrollbar-thumb:hover { background-color: black; } `; const AddColumnButton = styled.div` display: flex; flex-direction: row; justify-content: center; align-items: center; background-color: transparent; margin-top: 1.2rem; color: #6b7280; width: 12rem; gap: 4px; border-radius: 4px; :hover { color: #fff; } cursor: pointer; `; const AddIcon = styled.div``; const AddColumnText = styled.div` display: flex; justify-content: center; align-items: center; margin-left: 0.25rem; `; const LoadingContainer = styled.div` width: 100%; display: flex; justify-content: center; align-items: center; `; const addColumnButton = ( <AddColumnButton> <AddIcon> <i className="bi bi-plus-circle-fill"></i> </AddIcon> <AddColumnText>Add new Column</AddColumnText> </AddColumnButton> ); const [columns, setColumns] = useState([]); const [draggingTask, setDraggingTask] = useState(null); const [draggingColumn, setDraggingColumn] = useState(null); const [columnsLoading, setColumnsLoading] = useState(false); const addNewColumn = useCallback( (newColumn) => { setColumns([...columns, newColumn]); }, [columns], ); const removeColumn = useCallback( (columnIndex) => { setColumns((prevColumns) => prevColumns.filter((_, idx) => idx !== columnIndex), ); }, [columns, setColumns], ); const addTaskToColumn = useCallback( (columnIndex, newTask) => { setColumns((previousColumns) => { const updatedColumns = [...previousColumns]; updatedColumns[columnIndex] = { ...updatedColumns[columnIndex], tasks: [...updatedColumns[columnIndex].tasks, newTask], }; return updatedColumns; }); }, [setColumns], ); const updateTaskInColumn = useCallback( (columnIndex, updatedTask) => { setColumns((previousColumns) => { const updatedColumns = [...previousColumns]; updatedColumns[columnIndex] = { ...updatedColumns[columnIndex], tasks: updatedColumns[columnIndex].tasks.map((task) => task.id === updatedTask.id ? updatedTask : task, ), }; return updatedColumns; }); }, [setColumns], ); const removeTaskFromColumn = useCallback( (taskId, columnIndex) => { setColumns( columns.map((col, idx) => idx === columnIndex ? { ...col, tasks: col.tasks.filter((task) => task.id !== taskId) } : col, ), ); }, [columns, setColumns], ); const onTaskDragStart = (id, title, originalStatus, originalIndex) => { try { setDraggingTask({ id, title, originalStatus, originalIndex }); } catch (e) { console.log('onTaskDragStart', e); } }; const onTaskDragStop = (newStatus, index) => { const actionStatusPrefix = 'Task drag'; let newActionStatus = { id: actionStatusPrefix + draggingTask.id + draggingTask.originalStatus + draggingTask.originalIndex, status: `Saving task ${draggingTask.title} to status ${newStatus}`, }; try { addActionStatus(newActionStatus); // Create a deep copy of the statuses let statusesCopy = JSON.parse(JSON.stringify(columns)); // Access and remove the task from the original status using the original index const originalStatusObj = statusesCopy.find( (status) => status.name === draggingTask.originalStatus, ); const newStatusIndex = statusesCopy.findIndex( (status) => status.name === newStatus, ); if (!originalStatusObj) { console.log('Original status not found: ', draggingTask.originalStatus); return; } const removedTask = originalStatusObj.tasks.splice( draggingTask.originalIndex, 1, )[0]; // If the task was not found, you can choose how to handle it. In this case, we just log an error and return. if (!removedTask) { console.log('Task not found: ', draggingTask); return; } // Add the task to the new status at the given index const newStatusObj = statusesCopy.find( (status) => status.name === newStatus, ); if (!newStatusObj) { console.log('New status not found: ', newStatus); return; } const newIndex = index ?? newStatusObj.tasks.length; newStatusObj.tasks.splice(newIndex, 0, removedTask); Near.fakCalimeroCall(contract, 'move_task', { project_id: projectId, task_id: draggingTask.id, to_status_index: newStatusIndex, position: newIndex, }).then(() => { newActionStatus.status = `Saved task ${draggingTask.title} to status ${newStatus}`; addActionStatus(newActionStatus); console.log('contract updated'); }); setDraggingTask(null); setColumns(statusesCopy); } catch (e) { newActionStatus.status = `Error saving task ${draggingTask.title} to status ${newStatus}`; addActionStatus(newActionStatus); console.log('onTaskDragStop', e); } }; const onTaskDragOver = () => { // todo! remove or implement }; const onColumnDragStart = (index, name) => { try { setDraggingColumn({ index, name }); } catch (e) { console.log('onColumnDragStart', e); } }; const onColumnDragStop = (newPositionIndex) => { if (newPositionIndex === draggingColumn.index || draggingTask) { return; } const actionStatusPrefix = 'Column drag'; let newActionStatus = { id: actionStatusPrefix + draggingColumn.name + draggingColumn.index, status: `Saving column ${draggingColumn.name} to new position ${ newPositionIndex + 1 }`, seen: false, }; try { addActionStatus(newActionStatus); let statusDataCopy = swapByName( columns, draggingColumn.index, newPositionIndex, ); setColumns(statusDataCopy); Near.fakCalimeroCall(contract, 'reorder_statuses', { project_id: projectId, old_position: draggingColumn.index, new_position: newPositionIndex, }).then(() => { //updating status newActionStatus.status = `Saved column ${ draggingColumn.name } to new position ${newPositionIndex + 1}`; addActionStatus(newActionStatus); }); } catch (e) { //updating status newActionStatus.status = `Error saving column ${ draggingColumn.name } to new position ${newPositionIndex + 1}`; addActionStatus(newActionStatus); } }; const swapByName = (arr, index1, index2) => { let temp = arr[index1]; arr[index1] = arr[index2]; arr[index2] = temp; return arr; }; const createTask = useCallback( async (projectId, taskTitle, taskDescription) => { try { return Near.fakCalimeroCall(contract, 'create_task', { project_id: projectId, title: taskTitle, description: taskDescription, labels: [], }); } catch (e) { console.log('createTask', e); } }, [], ); const getStatuses = useCallback(async (projectId) => { setColumnsLoading(true); try { Near.asyncCalimeroView(contract, 'get_statuses', { project_id: projectId, }).then((data) => { setColumns(data); setColumnsLoading(false); }); } catch (e) { console.log('getStatuses', e); } }, []); useEffect(() => { if (projectId) { getStatuses(projectId); } }, [projectId]); return ( <> {columnsLoading ? ( <LoadingContainer> <Widget src={`${componentOwnerId}/widget/Calimero.Curb.Loader.Loader`} props={{ size: 128 }} /> </LoadingContainer> ) : ( <HorizontalFlexContainer> {columns.map((column, columnIndex) => { return ( <div key={columnIndex} draggable="true" value={columnIndex} onDragStart={() => onColumnDragStart(columnIndex, column.name)} onDrop={() => onColumnDragStop(columnIndex)} > <Widget src={`${componentOwnerId}/widget/Calimero.TaskChain.BoardContainer.Column`} props={{ columnIndex, componentOwnerId, title: column.name, tasks: column.tasks, onTaskDragStop: onTaskDragStop, onTaskDragStart: onTaskDragStart, onTaskDragOver: onTaskDragOver, createTask, projectId, contract, setColumns, columns, addActionStatus, removeColumn, removeTaskFromColumn, addTaskToColumn, updateTaskInColumn, }} /> </div> ); })} <AddColumnContainer> <Widget src={`${componentOwnerId}/widget/Calimero.TaskChain.Popups.AddColumnPopup`} props={{ componentOwnerId, addNewColumn, addColumnButton, addActionStatus, contract, projectId, }} /> </AddColumnContainer> </HorizontalFlexContainer> )} </> );