const { projectId, setSelectedProjectId, contract, status, componentOwnerId, addActionStatus } = props; const TopBar = styled.div` width: 100%; height: 50px; display: flex; flex-direction: row; `; 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%; `; 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 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 [saveInProgress, setSaveInProgress] = useState(false); const [draggingColumn, setDraggingColumn] = useState(null); const addNewColumn = useCallback((newColumn) => { setColumns([...columns, newColumn]); }, [columns]); const removeColumn = useCallback((columnIndex) => { setColumns(prevColumns => prevColumns.filter((_, idx) => idx !== columnIndex)); }, [columns, setColumns]); const handleColumns = (projectId) => { try { let columns = []; Near.asyncCalimeroView(contract, "get_statuses", { project_id: projectId, }).then((c) => { columns = c; const storageColumns = Storage.privateGet( "tempColumns" + contract + projectId ); if (storageColumns && JSON.parse(storageColumns).length > 0) { const storageArray = JSON.parse(storageColumns); const filteredStorageArray = storageArray.filter( (storageItem) => storageItem.project_id === projectId ); const newColumns = columns.map((col) => ({ name: col.name, tasks: [...col.tasks], })); for (let i = 0; i < columns.length; i++) { for (let newTask of filteredStorageArray) { if (newTask.status === columns[i].name) { if (columns[i].tasks.find((task) => task.id === newTask.id)) { newColumns[i].tasks = newColumns[i].tasks.map((task) => { if (task.id === newTask.id) { return newTask; } return task; }); } else if ( !columns[i].tasks.some((task) => task.title === newTask.title) ) { newColumns[i].tasks = [...newColumns[i].tasks, newTask]; } } } } setColumns(newColumns); } else { handleColumns(projectId); } }); } catch (e) { console.log(e); Storage.privateSet("tempColumns" + contract + projectId, ""); } }; const submitComment = (task_id, message) => { Near.fakCalimeroCall(contract, "comment_on_task", { project_id, task_id, message, }); }; const onTaskDragStart = (id, originalStatus, originalIndex) => { try { setDraggingTask({ id, originalStatus, originalIndex }); } catch (e) { console.log("onTaskDragStart", e); } }; const onTaskDragStop = (newStatus, index) => { try { // 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); // Update the state with the modified statuses setSaveInProgress(true); Near.fakCalimeroCall(contract, "move_task", { project_id: projectId, task_id: draggingTask.id, to_status_index: newStatusIndex, position: newIndex, }).then(() => { setSaveInProgress(false); console.log("contract updated"); }); setDraggingTask(null); setColumns(statusesCopy); } catch (e) { console.log("onTaskDragStop", e); } }; const onTaskDragOver = () => { try { } catch (e) { console.log("onTaskDragOver", e); } }; 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 }`, }; 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) => { try { Near.asyncCalimeroView(contract, "get_statuses", { project_id: projectId, }).then((data) => { setColumns(data); }); } catch (e) { console.log("getStatuses", e) } }, []); useEffect(() => { if (projectId) { getStatuses(projectId); } }, [projectId]) return ( <> <HorizontalFlexContainer> {columns.map((column, columnIndex) => { return ( <div key={columnIndex} draggable="true" droppable="true" value={columnIndex} onDragStart={() => onColumnDragStart(columnIndex, column.name)} onDrop={() => onColumnDragStop(columnIndex)} > <Widget src={`${componentOwnerId}/widget/Calimero.TaskChain.BoardContainer.TaskColumn`} props={{ columnIndex, componentOwnerId, title: column.name, tasks: column.tasks, submitComment: submitComment, onTaskDragStop: onTaskDragStop, onTaskDragStart: onTaskDragStart, onTaskDragOver: onTaskDragOver, createStatus, handleColumns, createTask, projectId, contract, setColumns, columns, addActionStatus, removeColumn }} /> </div> ); })} <AddColumnContainer> <Widget src={`${componentOwnerId}/widget/Calimero.TaskChain.Popups.AddColumnPopup`} props={{ componentOwnerId, addNewColumn, addColumnButton, addColumn, addActionStatus, contract, projectId }} /> </AddColumnContainer> </HorizontalFlexContainer> </> );