将看板中的可拖动元素存储到firebase

yhuiod9q  于 2023-11-21  发布在  其他
关注(0)|答案(1)|浏览(92)

我做了一个看板,包含3列(To do,Doing and Done).每一栏都有任务.我把每一个动作都连接到Firebase上(添加,删除或修改任务和列)。但只有一个动作,我不能连接到firebase,这是当我拖动一个任务之间的列,我希望该任务将其columnId更改为它已添加到列。实现了拖动,但它没有连接到Firebase。我将为您提供我的Firebase结构和所需的代码。Firebase:

columns (collection):
todo (document id): title (string field)
doing (document id): title (string field)
done (document id): title (string field)

tasks (collection): auto generated taskId: 
                                columnId: (string field) which determines the column 
                                content: (string field) content of the task

字符串
index.ts:类型

export type Id = string | number;
export type Column = { id: Id; title: string; };
export type Task = { id: Id; columnId: Id; content: string; };


tsx:所有事情发生的地方(这不是完整的代码)

function KanbanBoard() {
  const [columns, setColumns] = useState<Column[]>([]);
  const [tasks, setTasks] = useState<Task[]>([]);
  const [activeColumn, setActiveColumn] = useState<Column | null>(null);
  const [activeTask, setActiveTask] = useState<Task | null>(null);

return (
    <div
      className="
        m-auto
        flex
        min-h-screen
        w-full
        items-center
        overflow-x-auto
        overflow-y-hidden
        px-[40]
    ">
      <DndContext
        sensors={sensors}
        onDragStart={onDragStart}
        onDragEnd={onDragEnd}
        onDragOver={onDragOver}>
        <div className="m-auto flex gap-2">
          <div className="flex gap-4">
            {columns.map((col) => (
              <ColumnContainer
                key={col.id}
                column={col}
                updateColumn={updateColumn}
                createTask={createTask}
                tasks={tasks.filter((task) => task.columnId === col.id)}
                deleteTask={deleteTask}
                updateTask={updateTask}
              />
            ))}
          </div>
        </div>
        {typeof document !== "undefined" &&
          createPortal(
            <DragOverlay>
              {activeColumn && (
                <ColumnContainer
                  column={activeColumn}
                  updateColumn={updateColumn}
                  createTask={createTask}
                  deleteTask={deleteTask}
                  updateTask={updateTask}
                  tasks={tasks.filter(
                    (task) => task.columnId === activeColumn.id
                  )}
                />
              )}
              {activeTask && (
                <TaskCard
                  task={activeTask}
                  deleteTask={deleteTask}
                  updateTask={updateTask}
                />
              )}
            </DragOverlay>,
            document.body
          )}
      </DndContext>
    </div>
  );

function onDragEnd(event: DragEndEvent) {
    setActiveColumn(null);
    setActiveTask(null);

    const { active, over } = event;

    if (!over) return;
    const activeColumnId = active.id;
    const overColumnId = over.id;

    if (activeColumnId === overColumnId) return;

    setColumns((columns) => {
      const activeColumnIndex = columns.findIndex(
        (col) => col.id === activeColumnId
      );

      const overColumnIndex = columns.findIndex(
        (col) => col.id === overColumnId
      );

      return arrayMove(columns, activeColumnIndex, overColumnIndex);
    });
  }

  function onDragOver(event: DragOverEvent) {
    const { active, over } = event;
    if (!over) return;

    const activeId = active.id;
    const overId = over.id;

    if (activeId === overId) return;

    const isActiveATask = active.data.current?.type === "Task";
    const isOverATask = over.data.current?.type === "Task";

    if (!isActiveATask) return;

    //Dropping a Task over another task
    if (isActiveATask && isOverATask) {
      setTasks((tasks) => {
        const activeIndex = tasks.findIndex((t) => t.id === activeId);
        const overIndex = tasks.findIndex((t) => t.id === overId);

        tasks[activeIndex].columnId = tasks[overIndex].columnId;

        return arrayMove(tasks, activeIndex, overIndex);
      });
    }

    const isOverAColumn = over.data.current?.type === "Column";

    //Dropping a Task over a column
    if (isActiveATask && isOverAColumn) {
      setTasks((tasks) => {
        const activeIndex = tasks.findIndex((t) => t.id === activeId);

        tasks[activeIndex].columnId = overId;

        return arrayMove(tasks, activeIndex, activeIndex);
      });
    }
  }

  function onDragStart(event: DragStartEvent) {
    if (event.active.data.current?.type === "Column") {
      setActiveColumn(event.active.data.current.column);
      return;
    }

    if (event.active.data.current?.type === "Task") {
      setActiveTask(event.active.data.current.task);
      return;
    }
  }

  async function updateTask(id: Id, content: string) {
    const newTasks = tasks.map((task) => {
      if (task.id !== id) return task;
      return { ...task, content };
    });
    try {
      const taskRef = doc(db, "tasks", id.toString());
      await updateDoc(taskRef, {
        content,
      });
    } catch (error) {
      console.log(error);
    }

    setTasks(newTasks);
  }


ColumnContainer.tsx:

interface Props {
  column: Column;
  tasks: Task[];
  updateColumn: (id: Id, title: string) => void;
  createTask: (columnId: Id) => void;
  deleteTask: (id: Id) => void;
  updateTask: (id: Id, content: string) => void;
}

function ColumnContainer(props: Props) {
  const { column, updateColumn, createTask, tasks, deleteTask, updateTask } =
    props;

  const [editMode, setEditMode] = useState(false);
  const [localTitle, setLocalTitle] = useState(column.title);

  const taskIds = useMemo(() => {
    return tasks.map((task) => task.id);
  }, [tasks]);

  const {
    setNodeRef,
    attributes,
    listeners,
    transform,
    transition,
    isDragging,
  } = useSortable({
    id: column.id,
    data: {
      type: "Column",
      column,
    },
    disabled: editMode,
  });

  const style = {
    transition,
    transform: CSS.Transform.toString(transform),
  };

  const toggleEditMode = () => {
    setEditMode((prev) => !prev);
  };
  const saveColumnTitle = debounce(() => {
    updateColumn(column.id, localTitle);
  }, 0);

  useEffect(() => {
    if (editMode) {
      // If in edit mode, update local title immediately
      saveColumnTitle.flush();
    }
  }, [editMode]);

  if (isDragging) {
    return (
      <div
        ref={setNodeRef}
        style={style}
        data-column-id={column.id}
        className="
      bg-columnBackgroundColor
      opacity-40
      border-2
      border-rose-500
      w-[350px]
      h-[500px]
      max-h-[500px]
      rounded-md
      flex
      flex-col
      "></div>
    );
  }

  return (
    <div
      ref={setNodeRef}
      style={style}
      className="
      bg-columnBackgroundColor
      w-[350px]
      h-[500px]
      max-h-[500px]
      rounded-md
      flex
      flex-col
      ">
      {/* Column title */}
      <div
        {...attributes}
        {...listeners}
        onClick={() => {
          setEditMode(true);
        }}
        className="
        bg-mainBackgroundColor
        text-md
        h-[60px]
        cursor-grab
        rounded-md
        rounded-b-none
        p-3
        font-bold
        border-columnBackgroundColor
        border-4x
        flex
        items-center
        justify-between
      ">
        <div className="flex gap-2">
          {!editMode && column.title}
          {editMode && (
            <input
              className="bg-black focus:border-rose-500 border rounded outline-none px-2"
              value={localTitle}
              onChange={(e) => setLocalTitle(e.target.value)}
              autoFocus
              onBlur={() => {
                toggleEditMode();
                saveColumnTitle();
              }}
              onKeyDown={(e) => {
                if (e.key !== "Enter") return;
                toggleEditMode();
                saveColumnTitle();
              }}
            />
          )}
        </div>
      </div>

      {/* Column task container */}
      <div className="flex flex-grow flex-col gap-4 p-2 overflow-x-hidden overflow-y-auto">
        <SortableContext items={[0]}>
          {tasks.map((task) => (
            <TaskCard
              key={task.id}
              task={task}
              deleteTask={deleteTask}
              updateTask={updateTask}
            />
          ))}
        </SortableContext>
      </div>

      {/* Column footer */}
      <button
        onClick={() => {
          createTask(column.id);
        }}
        className="flex gap-2 items-center border-columnBackgroundColor boder-2 rounded-md p-4 border-x-columnBackgroundColor hover:bg-mainBackgroundColor hover:text-rose-500 active:bg-black">
        <PlusCircleIcon className="w-6 h-6" />
        Add task
      </button>
    </div>
  );
}

export default ColumnContainer;


如果你需要更多的信息,请让我知道。

2guxujil

2guxujil1#

我使用SQL Server. net 6 API作为后端做了我的工作,我将很快制作一个github repo来解释我是如何做到的,现在我将简要地解释一下...你有一个函数,处理拖动的卡类似... const handleDragEnd = async (dropResult: DropResult) => {然后获得拖动的卡数据const { source, destination, type } = dropResult;现在处理卡重新排序逻辑////检查卡是否在同一个列表中移动if (source.droppableId === destination.droppableId)你可以用很多方法来排序它,我做的方法是创建一个名为sequence的字段来保存卡的序列,所以每次我拖动卡片的时候,我都会发布一个API,并设置为2sec来更新拖动卡片的顺序,这取决于它是否在同一个列表中移动。

相关问题