const { contract } = props; const methods = ["move_task", "comment_on_task"]; State.init({ task: null, draggingTask: null, statusData: [], loggedIn: false, bootStrapping: true, }); const bootStrapApp = () => { Near.hasValidFak(contract).then((result) => { console.log("hasValidFak", result); if (result) { Near.asyncView(contract, "get_statuses", {}, "final", false).then( (data) => { State.update({ bootStrapping: false, loggedIn: true, statusData: data, }); } ); } else { State.update({ bootStrapping: false, loggedIn: false }); } }); }; console.log(JSON.stringify(state)); if (state.bootStrapping) { console.log("bootstrapping"); bootStrapApp(); return <Widget src="xabi.testnet/widget/Calimero.Loading" />; } if (!state.loggedIn) { return ( <Widget src="xabi.testnet/widget/Calimero.JoinApp" props={{ appName: "TaskChain", contractName: contract, methodNames: methods, }} /> ); } const submitComment = (task_id, message) => { Near.fakCall(contract, "comment_on_task", { task_id, message }); console.log("submit"); }; const onTaskDragStart = (id, originalStatus, originalIndex) => { console.log("drag start", id, originalStatus, originalIndex); State.update({ draggingTask: { id, originalStatus, originalIndex } }); }; const onTaskDragStop = (newStatus, index) => { console.log("drag stop", state.draggingTask); // Create a deep copy of the statuses let statusesCopy = JSON.parse(JSON.stringify(state.statusData)); // Access and remove the task from the original status using the original index const originalStatusObj = statusesCopy.find( (status) => status.name === state.draggingTask.originalStatus ); const newStatusIndex = statusesCopy.findIndex((status) => status.name === newStatus); if (!originalStatusObj) { console.log( "Original status not found: ", state.draggingTask.originalStatus ); return; } const removedTask = originalStatusObj.tasks.splice( state.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: ", state.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"); Near.fakCall(contract, "move_task", { task_id: state.draggingTask.id, to_status_index: newStatusIndex, position: newIndex }) .then(() => console.log("contract updated")); State.update({ draggingTask: null, statusData: statusesCopy }); }; const onTaskDragOver = () => { //console.log("drag over", state.draggingTask); }; const TaskCard = ({ task, status, index }) => { console.log("Card", status, index); const handleClick = () => { State.update({ task }); }; const title = task.title ?? ""; const taskId = task.id ?? ""; const Multiline = styled.div` max-height: 3em; overflow: hidden; position: relative; flex: 1; `; const Content = styled.div` display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis; flex: 1; `; const ImageContainer = styled.div` width: 30px; height: 30px; overflow: hidden; border-radius: 50%; `; const WidgetContent = styled.div` width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; `; const CircularImage = () => ( <Widget props={{ style: { width: "2em", height: "2em" } }} src="eugenethedream/widget/ProfileImage" /> ); return ( <> <div style={{ padding: "10px", border: "1px solid gray", borderRadius: "5px", width: "250px", height: "100px", backgroundColor: "white", }} draggable onDragStart={() => onTaskDragStart(taskId, status, index)} onDrop={() => onTaskDragStop(status, index)} onClick={handleClick} > <Multiline> <Content>{title}</Content> </Multiline> <div style={{ display: "flex", justifyContent: "space-between", marginTop: "8px", }} > <span>#{taskId}</span> <ImageContainer> <WidgetContent> <CircularImage /> </WidgetContent> </ImageContainer> </div> </div> </> ); }; const TaskDetailsDialog = ({ task, submitComment, children }) => { return (<Widget src="xabi.testnet/widget/TaskDetailsDialog" props={{ task, children, submitComment }} /> );}; const TaskColumn = (props) => { const tasks = props.tasks ?? []; const title = props.title ?? "Unknown title"; const ColumnContainer = styled.div` display: flex; flex-direction: column; height: 100%; min-width: 250px; margin-left: 2em; margin-right: 2em; background-color: transparent; `; const StyledLabelWrapper = styled.div` background-color: #212529; color: white; display: flex; justify-content: space-between; align-items: center; padding: 0.5em; width: 100%; `; return ( <ColumnContainer> <StyledLabelWrapper> <Label.Root>{title}</Label.Root> <button onClick={() => onTaskDragStop(title)}>+</button> </StyledLabelWrapper> <div droppable style={{ height: "100%" }} onDragOver={(e) => { onTaskDragOver(); }} onDrop={() => onTaskDragStop(title)} > {tasks.map((task, i) => ( <TaskDetailsDialog task={task} submitComment={submitComment}> <TaskCard task={task} status={title} index={i} /> </TaskDetailsDialog> ))} </div> </ColumnContainer> ); }; const HorizontalFlexContainer = styled.div` width: 100%; height: 100vh; display: flex; flex-direction: row; justify-content: space-between; background-color: #f2f2f2; /* Replace with your desired background color */ `; return ( <HorizontalFlexContainer> {state.statusData.map((el) => { return <TaskColumn title={el.name} tasks={el.tasks} />; })} </HorizontalFlexContainer> );