Merge branch 'develop'

This commit is contained in:
lizerk 2026-08-07 20:19:12 +03:00
commit 3b0b5f20b8
2 changed files with 110 additions and 27 deletions

View File

@ -1174,6 +1174,20 @@ html[data-bs-theme="dark"] .tasks-board-group-header-sticky {
собственную область прокрутки. contain не передаёт скролл всей странице,
когда пользователь дошёл до начала или конца конкретной ленты. */
@media (max-width: 79.999rem) {
.tasks-task-card,
.tasks-board-column-header,
.tasks-board-subcolumn-header {
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
}
.tasks-task-card img,
.tasks-board-column-header img,
.tasks-board-subcolumn-header img {
-webkit-user-drag: none;
}
.tasks-board-column-header,
.tasks-board-subcolumn-header {
box-shadow: none !important;

View File

@ -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>
@ -2128,10 +2132,10 @@ function AddColumnCard({
<button
type="button"
onClick={onClick}
style={{ height: cardHeight }}
className="group flex min-w-[18rem] self-start flex-col items-center justify-center rounded-xl border border-dashed border-[var(--border-subtle)] bg-[var(--surface-muted)] p-4 text-center transition hover:border-[var(--accent-primary)] hover:bg-[var(--surface-elevated)] xl:sticky xl:top-0"
style={{ "--tasks-add-column-height": `${cardHeight}px` } as CSSProperties}
className="group flex h-16 min-h-16 min-w-[18rem] self-start items-center justify-center gap-2 rounded-xl border border-dashed border-[var(--border-subtle)] bg-[var(--surface-muted)] px-4 text-center transition hover:border-[var(--accent-primary)] hover:bg-[var(--surface-elevated)] xl:sticky xl:top-0 xl:h-[var(--tasks-add-column-height)] xl:flex-col xl:gap-0 xl:p-4"
>
<span className="app-selected mb-3 flex h-10 w-10 items-center justify-center rounded-xl transition group-hover:scale-105">
<span className="app-selected flex h-9 w-9 items-center justify-center rounded-xl transition group-hover:scale-105 xl:mb-3 xl:h-10 xl:w-10">
<Plus size={18} />
</span>
<span className="text-sm font-semibold text-[var(--foreground)]">
@ -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>
);
})}