feat(tasks): split mobile board lanes by column
This commit is contained in:
parent
3d92e992af
commit
7a0270fb32
@ -1738,6 +1738,7 @@ type BoardTaskCellProps = TaskColumnCardsProps & {
|
||||
linkedColumnHighlight: boolean;
|
||||
synchronizedColumnDrag: boolean;
|
||||
fillAvailableHeight: boolean;
|
||||
embeddedInMobileLane?: boolean;
|
||||
};
|
||||
|
||||
function BoardTaskCell({
|
||||
@ -1746,6 +1747,7 @@ function BoardTaskCell({
|
||||
linkedColumnHighlight,
|
||||
synchronizedColumnDrag,
|
||||
fillAvailableHeight,
|
||||
embeddedInMobileLane = false,
|
||||
...cardsProps
|
||||
}: BoardTaskCellProps) {
|
||||
const { setNodeRef, isOver } = useDroppable({
|
||||
@ -1764,12 +1766,14 @@ function BoardTaskCell({
|
||||
return (
|
||||
<section
|
||||
ref={setNodeRef}
|
||||
className={`tasks-mobile-lane-viewport flex w-full min-w-0 flex-col transition ${
|
||||
rowId ? "min-h-0" : "min-h-[28rem]"
|
||||
className={`flex w-full min-w-0 flex-col transition ${
|
||||
embeddedInMobileLane
|
||||
? ""
|
||||
: `tasks-mobile-lane-viewport ${rowId ? "min-h-0" : "min-h-[28rem]"}`
|
||||
} xl:w-full ${fillAvailableHeight ? "xl:min-h-full" : ""}`}
|
||||
>
|
||||
<div
|
||||
className={`tasks-mobile-lane-scroll tasks-column-scroll min-h-0 flex-1 space-y-2 overflow-y-auto rounded-xl border bg-[var(--surface-primary)] p-3 transition xl:flex-none xl:overflow-visible ${columnBorderClass}`}
|
||||
className={`${embeddedInMobileLane ? "" : "tasks-mobile-lane-scroll overflow-y-auto xl:flex-none"} tasks-column-scroll min-h-0 flex-1 space-y-2 rounded-xl border bg-[var(--surface-primary)] p-3 transition xl:overflow-visible ${columnBorderClass}`}
|
||||
>
|
||||
<TaskColumnCards {...cardsProps} />
|
||||
</div>
|
||||
@ -3700,6 +3704,7 @@ function TasksPageContent() {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [search, setSearch] = useState("");
|
||||
const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
|
||||
const [mobileLaneLayout, setMobileLaneLayout] = useState(false);
|
||||
const [onlyMine, setOnlyMine] = useState(false);
|
||||
const [desktopWideMode, setDesktopWideMode] = useState(false);
|
||||
const [selectedColumnTarget, setSelectedColumnTarget] = useState<number | "all">("all");
|
||||
@ -3897,6 +3902,14 @@ function TasksPageContent() {
|
||||
);
|
||||
}, [user?.id]);
|
||||
|
||||
useEffect(() => {
|
||||
const mediaQuery = window.matchMedia("(max-width: 79.999rem)");
|
||||
const updateMobileLaneLayout = () => setMobileLaneLayout(mediaQuery.matches);
|
||||
updateMobileLaneLayout();
|
||||
mediaQuery.addEventListener("change", updateMobileLaneLayout);
|
||||
return () => mediaQuery.removeEventListener("change", updateMobileLaneLayout);
|
||||
}, []);
|
||||
|
||||
const changeDesktopWideMode = useCallback((enabled: boolean) => {
|
||||
setDesktopWideMode(enabled);
|
||||
if (user?.id) {
|
||||
@ -7738,9 +7751,10 @@ function TasksPageContent() {
|
||||
const renderTaskCell = (
|
||||
cellColumn: TaskColumn,
|
||||
rowId: number | null,
|
||||
embeddedInMobileLane = false,
|
||||
) => (
|
||||
<BoardTaskCell
|
||||
key={`cell-${cellColumn.id}:${rowId ?? "base"}`}
|
||||
key={`cell-${cellColumn.id}:${rowId ?? "base"}:${embeddedInMobileLane ? "mobile" : "desktop"}`}
|
||||
column={cellColumn}
|
||||
rowId={rowId}
|
||||
tasks={tasksByPlacement.get(taskPlacementKey(cellColumn.id, rowId)) || []}
|
||||
@ -7757,6 +7771,7 @@ function TasksPageContent() {
|
||||
linkedColumnHighlight={activeColumnDragId === cellColumn.id}
|
||||
synchronizedColumnDrag={activeColumnDragId !== null}
|
||||
fillAvailableHeight={desktopWideMode && rowId === null}
|
||||
embeddedInMobileLane={embeddedInMobileLane}
|
||||
quickTaskComposer={renderQuickTaskComposer(cellColumn, rowId)}
|
||||
/>
|
||||
);
|
||||
@ -7826,31 +7841,85 @@ function TasksPageContent() {
|
||||
subcolumnHeaders={subcolumnHeaders}
|
||||
subcolumnGridTemplate={gridTemplateColumns}
|
||||
>
|
||||
{baseColumns}
|
||||
{columnRows.length > 0 ? (
|
||||
<div className="flex min-w-0 flex-col gap-2">
|
||||
{columnRows.map((row, rowIndex) => {
|
||||
const rowTasksCount = filteredTasks.filter((task) => task.row === row.id).length;
|
||||
{mobileLaneLayout ? (
|
||||
<div
|
||||
className="grid w-max min-w-0 gap-2"
|
||||
style={{ gridTemplateColumns }}
|
||||
>
|
||||
{previewCellColumns.map((cellColumn) => {
|
||||
const baseCellTasksCount = tasksByPlacement.get(
|
||||
taskPlacementKey(cellColumn.id, null),
|
||||
)?.length || 0;
|
||||
return (
|
||||
<BoardColumnRowView
|
||||
key={row.id}
|
||||
row={{ ...row, tasks_count: rowTasksCount }}
|
||||
gridTemplateColumns={gridTemplateColumns}
|
||||
onCreateTask={() => {
|
||||
if (cellColumns[0]) startQuickTask(cellColumns[0].id, row.id);
|
||||
}}
|
||||
onEdit={() => openEditColumnRow(row)}
|
||||
onDelete={() => void deleteColumnRow(row)}
|
||||
expanded={!boardViewState.collapsed_row_ids.includes(row.id)}
|
||||
onToggle={() => toggleBoardViewState("row", row.id)}
|
||||
hasFollowingSection={rowIndex < columnRows.length - 1}
|
||||
<section
|
||||
key={`mobile-lane-${cellColumn.id}`}
|
||||
className="tasks-mobile-lane-viewport flex w-full min-w-0 flex-col"
|
||||
>
|
||||
{previewCellColumns.map((item) => renderTaskCell(item, row.id))}
|
||||
</BoardColumnRowView>
|
||||
<div className="tasks-mobile-lane-scroll tasks-column-scroll min-h-0 flex-1 space-y-2 overflow-y-auto">
|
||||
<BoardBaseColumnsView
|
||||
collapsible={columnRows.length > 0}
|
||||
tasksCount={baseCellTasksCount}
|
||||
gridTemplateColumns={TASK_SUBCOLUMN_WIDTH}
|
||||
expanded={!boardViewState.collapsed_base_column_ids.includes(column.id)}
|
||||
onToggle={() => toggleBoardViewState("base", column.id)}
|
||||
hasFollowingSection={columnRows.length > 0}
|
||||
>
|
||||
{renderTaskCell(cellColumn, null, true)}
|
||||
</BoardBaseColumnsView>
|
||||
{columnRows.map((row, rowIndex) => {
|
||||
const rowTasksCount = tasksByPlacement.get(
|
||||
taskPlacementKey(cellColumn.id, row.id),
|
||||
)?.length || 0;
|
||||
return (
|
||||
<BoardColumnRowView
|
||||
key={`${cellColumn.id}:${row.id}`}
|
||||
row={{ ...row, tasks_count: rowTasksCount }}
|
||||
gridTemplateColumns={TASK_SUBCOLUMN_WIDTH}
|
||||
onCreateTask={() => startQuickTask(cellColumn.id, row.id)}
|
||||
onEdit={() => openEditColumnRow(row)}
|
||||
onDelete={() => void deleteColumnRow(row)}
|
||||
expanded={!boardViewState.collapsed_row_ids.includes(row.id)}
|
||||
onToggle={() => toggleBoardViewState("row", row.id)}
|
||||
hasFollowingSection={rowIndex < columnRows.length - 1}
|
||||
>
|
||||
{renderTaskCell(cellColumn, row.id, true)}
|
||||
</BoardColumnRowView>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
) : (
|
||||
<>
|
||||
{baseColumns}
|
||||
{columnRows.length > 0 ? (
|
||||
<div className="flex min-w-0 flex-col gap-2">
|
||||
{columnRows.map((row, rowIndex) => {
|
||||
const rowTasksCount = filteredTasks.filter((task) => task.row === row.id).length;
|
||||
return (
|
||||
<BoardColumnRowView
|
||||
key={row.id}
|
||||
row={{ ...row, tasks_count: rowTasksCount }}
|
||||
gridTemplateColumns={gridTemplateColumns}
|
||||
onCreateTask={() => {
|
||||
if (cellColumns[0]) startQuickTask(cellColumns[0].id, row.id);
|
||||
}}
|
||||
onEdit={() => openEditColumnRow(row)}
|
||||
onDelete={() => void deleteColumnRow(row)}
|
||||
expanded={!boardViewState.collapsed_row_ids.includes(row.id)}
|
||||
onToggle={() => toggleBoardViewState("row", row.id)}
|
||||
hasFollowingSection={rowIndex < columnRows.length - 1}
|
||||
>
|
||||
{previewCellColumns.map((item) => renderTaskCell(item, row.id))}
|
||||
</BoardColumnRowView>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</BoardColumnGroup>
|
||||
);
|
||||
})}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user