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> </> );