const { projectId, contract, showAddTaskDialog, taskTitle, taskDescription, onChangeTaskTitle, onChangeTaskDescription, onChangeShowAddTaskDialog, functionLoader, status, onChangeFunctionLoader, clearTaskTitle, clearTaskDescription, showEditTaskDialog, onChangeShowEditTaskDialog, onEditTaskTitle, onEditTaskDescription, setEditTask, editTaskTitle, editTaskDescription, editTaskId, editTaskColumn, editTaskDialogTaskId, onChangeShowDeleteTaskDialog, showDeleteTaskDialog, deleteTaskId, deleteTaskStatus, onChangeDeleteTaskStatus, componentOwnerId, editTaskTitleMissing, editTaskDescriptionMissing, setEditTaskTitleMissing, setEditTaskDescriptionMissing, addTaskTitleMissing, addTaskDescriptionMissing, setAddTaskTitleMissing, setAddTaskDescriptionMissing, addColumnDialogOpen, setAddColumnDialogOpen, addColumn, onChangeColumnName, columnName, addColumnNameMissing, setColumnName } = 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 AddColumnButton = styled.div` display: flex; flex-direction: row; justify-content: center; align-items: center; background-color: transparent; color: #6B7280; width: 12rem; padding: 16px; gap: 4px; border-radius: 4px; :hover { color: #fff; } `; const AddIcon = styled.div` `; const HorizontalFlexContainer = styled.div` height: 100vh; display: flex; flex-direction: row; justify-content: space-between; `; const setColumns = () => { 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]; } } } } setStatusData(newColumns) } else { setColumns(); } }) } catch (e) { console.log(e); Storage.privateSet("tempColumns" + contract + projectId, ""); } } const [statusData, setStatusData] = useState([]); const [createStatus, setCreateStatus] = useState([]); const [draggingTask, setDraggingTask] = useState(null); const [saveInProgress, setSaveInProgress] = useState(false); const [currentProjectId, setCurrentProjectId] = useState(null); const [editTaskTitleStatus, setEditTaskTitleStatus] = useState([]); const [editTaskDescriptionStatus, setEditTaskDescriptionStatus] = useState([]); const getStatuses = () => { Near.asyncCalimeroView( contract, "get_statuses", { project_id: projectId } ).then( ( data ) => { setCurrentProjectId(projectId) setStatusData(data); } ); }; const createTask = () => { let isTitleMissing = false; let isDescriptionMissing = false; if (!taskTitle || taskTitle.trim() === '') { setAddTaskTitleMissing(true); isTitleMissing = true; } if (!taskDescription || taskDescription.trim() === '') { setAddTaskDescriptionMissing(true); isDescriptionMissing = true; } if(isTitleMissing || isDescriptionMissing) { return; } try { onChangeFunctionLoader(true); setCreateStatus([...createStatus, {title: taskTitle, status: "Saving..."}]); const newTask = { project_id: projectId, title: taskTitle, description: taskDescription, //for now first status status: statusData[0].name }; let newColumnsArray = []; const storageColumns = Storage.privateGet( "tempColumns" + contract + currentProjectId ); if (storageColumns && JSON.parse(storageColumns).length > 0) { newColumnsArray = JSON.parse(storageColumns); newColumnsArray.push(newTask); } else { newColumnsArray.push(newTask); } const jsonStringArray = JSON.stringify(newColumnsArray); Storage.privateSet( "tempColumns" + contract + currentProjectId, jsonStringArray ); setColumns(); Near.fakCalimeroCall( contract, "create_task", { project_id: projectId, title: taskTitle, description: taskDescription, labels: [], } ).then(() => { let foundStatus = createStatus.find(s => s.title === taskTitle); foundStatus.status = "Saved"; const filteredStatuses = createStatus.filter(s => s.title !== taskTitle); setCreateStatus([...filteredStatuses, foundStatus]); setTimeout(() => cleanTaskStatus(taskTitle), 3000); }) } catch (e) { let foundStatus = createStatus.find(s => s.title === taskTitle); foundStatus.status = "Error saving"; const filteredStatuses = createStatus.filter(s => s.title !== taskTitle); setCreateStatus([...filteredStatuses, foundStatus]); Storage.privateSet( "tempColumns" + contract + currentProjectId, "" ); } onChangeFunctionLoader(false); onChangeShowAddTaskDialog(false); clearTaskTitle(); clearTaskDescription(); }; const deleteTask = () => { const oldStatuses = statusData; const taskId = JSON.parse(JSON.stringify(deleteTaskId)); const newDeleteTaskStatus = [...deleteTaskStatus, {id: taskId, status: `Deleting task: ${taskId}`}] onChangeDeleteTaskStatus(newDeleteTaskStatus); try { const newStatuses = statusData; let deleteTask; for (let status of newStatuses) { if (status.tasks.find(task => task.id === taskId)) { deleteTask = status.tasks.find(task => task.id === taskId); status.tasks = status.tasks.filter(task => task.id !== taskId) } } setStatusData(newStatuses) Near.fakCalimeroCall( contract, "delete_task_by_id", { project_id: projectId, task_id: taskId } ).then((res) => { let foundStatus = newDeleteTaskStatus.find(t => t.id === taskId); foundStatus.status = `Deleted task: ${taskId}`; const filteredStatuses = newDeleteTaskStatus.filter(t => t.id !== taskId); onChangeDeleteTaskStatus([...filteredStatuses, foundStatus]) setTimeout(() => cleanDeleteTaskStatus(taskId), 3000); }) } catch (e) { let foundStatus = newDeleteTaskStatus.find(t => t.id === taskId); foundStatus.status = `Error deleting task: ${taskId}`; const filteredStatuses = newDeleteTaskStatus.filter(t => t.id !== taskId); onChangeDeleteTaskStatus([...filteredStatuses, foundStatus]); setStatusData(oldStatuses); setTimeout(getStatuses, 3000); } onChangeShowDeleteTaskDialog(false, undefined) onChangeShowEditTaskDialog(false, undefined) }; const updateTaskTitle = () => { if (!!editTaskTitle) { const oldTask = statusData.flatMap(status => status.tasks).find(task => task.id === editTaskId); if (oldTask.title !== editTaskTitle) { try { setEditTaskTitleStatus([...editTaskTitleStatus, {id: editTaskId, status: "Saving..."}]); const editedTask = { project_id: projectId, id: editTaskId, title: editTaskTitle, description: editTaskDescription, status: editTaskColumn }; let newColumnsArray = []; const storageColumns = Storage.privateGet( "tempColumns" + contract + projectId ); if (storageColumns && JSON.parse(storageColumns).length > 0) { const columnsArray = JSON.parse(storageColumns); let filteredColumnsArray = []; if (columnsArray.find(task => task.id === editTaskId)) { filteredColumnsArray = columnsArray.filter(task => task.id !== editTaskId) } if (columnsArray.find(task => task.title === oldTask.title)) { filteredColumnsArray = columnsArray.filter(task => task.title !== oldTask.title) } newColumnsArray = filteredColumnsArray; newColumnsArray.push(editedTask); } else { newColumnsArray.push(editedTask); } const jsonStringArray = JSON.stringify(newColumnsArray); Storage.privateSet( "tempColumns" + contract + projectId, jsonStringArray ); setColumns(); Near.fakCalimeroCall( contract, "change_task_name", { project_id: projectId, task_id: editTaskId, new_title: editTaskTitle } ).then( () => { let foundStatus = editTaskTitleStatus.find(t => t.id === editTaskId); foundStatus.status = "Saved"; const filteredStatuses = editTaskTitleStatus.filter(t => t.id !== editTaskId); setEditTaskTitleStatus([...filteredStatuses, foundStatus]); setTimeout(getStatuses, 3000); setTimeout(() => cleanEditTaskTitleStatus(editTaskId), 3000); } ); } catch (e) { let foundStatus = editTaskTitleStatus.find(t => t.id === editTaskId); foundStatus.status = "Error saving"; const filteredStatuses = editTaskTitleStatus.filter(t => t.id !== editTaskId); setEditTaskTitleStatus([...filteredStatuses, foundStatus]); setTimeout(getStatuses, 3000); setTimeout(() => cleanEditTaskTitleStatus(editTaskId), 3000); } } } else { setEditTaskTitleMissing(true); } }; const cleanTaskStatus = (title) => { const filteredStatuses = createStatus.filter(s => s.title !== title); setCreateStatus(filteredStatuses) } const cleanDeleteTaskStatus = (id) => { const filteredStatuses = deleteTaskStatus.filter(t => t.id !== id); onChangeDeleteTaskStatus(filteredStatuses) } const updateTaskDescription = () => { if (!!editTaskDescription) { const oldTask = statusData.flatMap(status => status.tasks).find(task => task.id === editTaskId); if (oldTask.description !== editTaskDescription) { try { setEditTaskDescriptionStatus([...editTaskDescriptionStatus, {id: editTaskId, status: "Saving..."}]); const editedTask = { project_id: projectId, id: editTaskId, title: editTaskTitle, description: editTaskDescription, status: editTaskColumn }; let newColumnsArray = []; const storageColumns = Storage.privateGet( "tempColumns" + contract + projectId ); const oldTask = statusData.flatMap(status => status.tasks).find(task => task.id === editTaskId); if (storageColumns && JSON.parse(storageColumns).length > 0) { const columnsArray = JSON.parse(storageColumns); let filteredColumnsArray = []; if (columnsArray.find(task => task.id === editTaskId)) { filteredColumnsArray = columnsArray.filter(task => task.id !== editTaskId) } if (columnsArray.find(task => task.title === oldTask.title)) { filteredColumnsArray = columnsArray.filter(task => task.title !== oldTask.title) } newColumnsArray = filteredColumnsArray; newColumnsArray.push(editedTask); } else { newColumnsArray.push(editedTask); } const jsonStringArray = JSON.stringify(newColumnsArray); Storage.privateSet( "tempColumns" + contract + projectId, jsonStringArray ); setColumns(); Near.fakCalimeroCall( contract, "edit_task_description", { project_id: projectId, task_id: editTaskId, new_description: editTaskDescription } ).then( () => { let foundStatus = editTaskDescriptionStatus.find(t => t.id === editTaskId); foundStatus.status = "Saved"; const filteredStatuses = editTaskDescriptionStatus.filter(t => t.id !== editTaskId); setEditTaskDescriptionStatus([...filteredStatuses, foundStatus]); setTimeout(getStatuses, 3000); setTimeout(() => cleanEditTaskDescriptionStatus(editTaskId), 3000); } ); } catch (e) { let foundStatus = editTaskDescriptionStatus.find(t => t.id === editTaskId); foundStatus.status = "Error saving"; const filteredStatuses = editTaskDescriptionStatus.filter(t => t.id !== editTaskId); setEditTaskDescriptionStatus([...filteredStatuses, foundStatus]); setTimeout(getStatuses, 3000); setTimeout(() => cleanEditTaskDescriptionStatus(editTaskId), 3000); } } } else { setEditTaskDescriptionMissing(true); } }; const cleanTaskStatusById = (id) => { const filteredStatuses = createStatus.filter(s => s.id !== id); setCreateStatus(filteredStatuses) } const cleanEditTaskTitleStatus = (id) => { const filteredStatuses = editTaskTitleStatus.filter(s => s.id !== id); setEditTaskTitleStatus(filteredStatuses); } const cleanEditTaskDescriptionStatus = (id) => { const filteredStatuses = editTaskDescriptionStatus.filter(s => s.id !== id); setEditTaskDescriptionStatus(filteredStatuses); } if ( currentProjectId !== projectId ) { getStatuses(); } const submitComment = ( task_id, message ) => { Near.fakCalimeroCall( contract, "comment_on_task", { project_id, task_id, message } ); console.log( "submit" ); }; const onTaskDragStart = ( id, originalStatus, originalIndex ) => { try { console.log( "drag start", id, originalStatus, originalIndex ); setDraggingTask({ id, originalStatus, originalIndex }) } catch (e) { console.log("onTaskDragStart", e); } }; const onTaskDragStop = ( newStatus, index ) => { try { console.log( "drag stop", draggingTask ); // Create a deep copy of the statuses let statusesCopy = JSON.parse( JSON.stringify( statusData ) ); // 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 console.log( "contract updated" ); 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) setStatusData(statusesCopy) } catch (e) { console.log("onTaskDragStop", e); }; }; const onTaskDragOver = () => { try { console.log("drag over", draggingTask); } catch (e) { console.log("onTaskDragOver", e); } }; const handleClick = () => { State.update( { task } ); }; const AddColumnText = styled.div` display: flex; justify-content: center; align-items: center; margin-left: 0.25rem `; return ( <> {addColumnDialogOpen && <Widget src={`${componentOwnerId}/widget/Calimero.TaskChain.BoardContainer.AddColumnDialog`} props={ { componentOwnerId, addColumn, setAddColumnDialogOpen, functionLoader, onChangeColumnName, columnName, addColumnNameMissing, setColumnName } } /> } <HorizontalFlexContainer> { statusData.map( ( el, index ) => { return ( <div key={ index }> <Widget src={`${componentOwnerId}/widget/Calimero.TaskChain.BoardContainer.TaskColumn`} props={ { componentOwnerId, title: el.name, tasks: el.tasks, submitComment: submitComment, onClick: handleClick, onTaskDragStop: onTaskDragStop, onTaskDragStart: onTaskDragStart, onTaskDragOver: onTaskDragOver, createTask, showAddTaskDialog, taskTitle, taskDescription, onChangeTaskTitle, onChangeTaskDescription, onChangeShowAddTaskDialog, functionLoader, createStatus, showEditTaskDialog, onChangeShowEditTaskDialog, onEditTaskTitle, onEditTaskDescription, setEditTask, editTaskTitle, editTaskDescription, updateTaskTitle, updateTaskDescription, editTaskTitleStatus, editTaskId, editTaskDialogTaskId, editTaskDescriptionStatus, onChangeShowDeleteTaskDialog, showDeleteTaskDialog, deleteTask, editTaskTitleMissing, editTaskDescriptionMissing, addTaskTitleMissing, addTaskDescriptionMissing } } /> </div> ); } ) } <AddColumnContainer> <AddColumnButton onClick={() => setAddColumnDialogOpen(true)}> <AddIcon> <i class="bi bi-plus-circle-fill"></i> </AddIcon> <AddColumnText>Add new Column</AddColumnText> </AddColumnButton> </AddColumnContainer> </HorizontalFlexContainer> </> );