Розробка Kanban-дошки для управління завданнями на сайті

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Розробка Kanban-дошки для управління завданнями на сайті
Середня
~5 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Розробка Kanban-дошок для управління завданнями на веб-сайтах

Kanban-доска — набір колонок (To Do, In Progress, Done) із карточками завдань, які можна перетягувати між колонками. Потребує drag-and-drop, оптимістичних оновлень стану та синхронізації з бекендом.

DnD Kit — рекомендований підхід

npm install @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities
import {
  DndContext, DragOverlay, DragStartEvent, DragEndEvent,
  PointerSensor, useSensor, useSensors, closestCorners
} from '@dnd-kit/core';
import {
  SortableContext, verticalListSortingStrategy,
  useSortable, arrayMove
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';

interface Task { id: string; title: string; columnId: string; order: number; }
interface Column { id: string; title: string; color: string; }

function KanbanBoard({ initialColumns, initialTasks, onTaskMove }) {
  const [tasks, setTasks] = useState<Task[]>(initialTasks);
  const [activeTask, setActiveTask] = useState<Task | null>(null);

  const sensors = useSensors(
    useSensor(PointerSensor, {
      activationConstraint: { distance: 8 }
    })
  );

  const handleDragStart = ({ active }: DragStartEvent) => {
    setActiveTask(tasks.find(t => t.id === active.id) ?? null);
  };

  const handleDragEnd = ({ active, over }: DragEndEvent) => {
    setActiveTask(null);
    if (!over) return;

    const activeTask = tasks.find(t => t.id === active.id);
    if (!activeTask) return;

    const overId = over.id as string;
    const overColumn = initialColumns.find(c => c.id === overId);
    const overTask = tasks.find(t => t.id === overId);

    const targetColumnId = overColumn?.id ?? overTask?.columnId ?? activeTask.columnId;
    const isMovingColumn = targetColumnId !== activeTask.columnId;

    setTasks(prev => {
      const updated = prev.map(t =>
        t.id === activeTask.id ? { ...t, columnId: targetColumnId } : t
      );

      if (!isMovingColumn && overTask) {
        const oldIndex = prev.findIndex(t => t.id === activeTask.id);
        const newIndex = prev.findIndex(t => t.id === overId);
        return arrayMove(updated, oldIndex, newIndex);
      }

      return updated;
    });

    onTaskMove(activeTask.id, targetColumnId).catch(() => {
      setTasks(initialTasks);
    });
  };

  return (
    <DndContext sensors={sensors} collisionDetection={closestCorners}
      onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
      <div className="kanban-board flex gap-4 overflow-x-auto p-4">
        {initialColumns.map(column => (
          <KanbanColumn
            key={column.id}
            column={column}
            tasks={tasks.filter(t => t.columnId === column.id)}
          />
        ))}
      </div>

      <DragOverlay>
        {activeTask && <TaskCard task={activeTask} isDragging />}
      </DragOverlay>
    </DndContext>
  );
}

Колонка і карточка

function KanbanColumn({ column, tasks }) {
  const taskIds = tasks.map(t => t.id);

  return (
    <div className="kanban-column w-72 shrink-0 bg-gray-50 rounded-xl p-3">
      <div className="flex items-center justify-between mb-3">
        <div className="flex items-center gap-2">
          <div className="w-3 h-3 rounded-full" style={{ background: column.color }} />
          <h3 className="font-semibold text-gray-700">{column.title}</h3>
          <span className="text-xs bg-gray-200 text-gray-500 rounded-full px-2 py-0.5">
            {tasks.length}
          </span>
        </div>
        <AddTaskButton columnId={column.id} />
      </div>

      <SortableContext items={taskIds} strategy={verticalListSortingStrategy}>
        <div className="space-y-2 min-h-20">
          {tasks.map(task => (
            <SortableTaskCard key={task.id} task={task} />
          ))}
        </div>
      </SortableContext>
    </div>
  );
}

function SortableTaskCard({ task }) {
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } =
    useSortable({ id: task.id });

  const style = {
    transform: CSS.Transform.toString(transform),
    transition,
    opacity: isDragging ? 0.5 : 1
  };

  return (
    <div ref={setNodeRef} style={style} {...attributes} {...listeners}>
      <TaskCard task={task} />
    </div>
  );
}

function TaskCard({ task, isDragging = false }) {
  return (
    <div className={`bg-white rounded-lg border border-gray-200 p-3 shadow-sm
      hover:shadow-md transition-shadow cursor-grab active:cursor-grabbing
      ${isDragging ? 'shadow-xl ring-2 ring-blue-300' : ''}`}>
      <p className="text-sm font-medium text-gray-800 mb-2">{task.title}</p>
      <div className="flex items-center justify-between">
        <PriorityBadge priority={task.priority} />
        {task.assignee && <AssigneeAvatar user={task.assignee} />}
      </div>
      {task.dueDate && (
        <p className={`text-xs mt-1 ${isPast(task.dueDate) ? 'text-red-500' : 'text-gray-400'}`}>
          📅 {format(task.dueDate, 'dd.MM')}
        </p>
      )}
    </div>
  );
}

Фільтри та пошук

function KanbanWithFilters({ board }) {
  const [filters, setFilters] = useState({
    assignee: null, priority: null, search: ''
  });

  const filteredTasks = board.tasks.filter(task => {
    if (filters.search && !task.title.toLowerCase().includes(filters.search.toLowerCase())) {
      return false;
    }
    if (filters.assignee && task.assigneeId !== filters.assignee) return false;
    if (filters.priority && task.priority !== filters.priority) return false;
    return true;
  });

  return (
    <div>
      <KanbanFilters filters={filters} onChange={setFilters} />
      <KanbanBoard tasks={filteredTasks} columns={board.columns} />
    </div>
  );
}

Часові межи

Базова Kanban-доска з DnD та синхронізацією з бекендом — 1–2 тижні. Повнофункціональна з фільтрами, підзавданнями, коментарями та real-time оновленнями — 3–4 тижні.