6.19.8: Кнопки сохранения досок внизу экрана
Some checks failed
Build and Push Docker Image / build-and-push (push) Failing after 1m16s

This commit is contained in:
poignatov
2026-03-18 16:59:18 +03:00
parent b1f4fdd449
commit 3a06d9148c
5 changed files with 102 additions and 40 deletions

View File

@@ -1 +1 @@
6.19.7
6.19.8

View File

@@ -1,6 +1,6 @@
{
"name": "play-life-web",
"version": "6.19.7",
"version": "6.19.8",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1166,7 +1166,7 @@ function AppContent() {
}
// Определяем, нужно ли скрывать нижнюю панель (для fullscreen экранов)
const isFullscreenTab = activeTab === 'test' || activeTab === 'purchase' || activeTab === 'add-words' || activeTab === 'task-form' || activeTab === 'wishlist-form' || activeTab === 'wishlist-detail' || activeTab === 'todoist-integration' || activeTab === 'telegram-integration' || activeTab === 'fitbit-integration' || activeTab === 'full' || activeTab === 'priorities' || activeTab === 'words' || activeTab === 'dictionaries' || activeTab === 'tracking' || activeTab === 'tracking-access' || activeTab === 'tracking-invite' || activeTab === 'shopping' || activeTab === 'shopping-item-form' || activeTab === 'shopping-board-form' || activeTab === 'shopping-board-join' || activeTab === 'shopping-item-history'
const isFullscreenTab = activeTab === 'test' || activeTab === 'purchase' || activeTab === 'add-words' || activeTab === 'task-form' || activeTab === 'wishlist-form' || activeTab === 'wishlist-detail' || activeTab === 'todoist-integration' || activeTab === 'telegram-integration' || activeTab === 'fitbit-integration' || activeTab === 'full' || activeTab === 'priorities' || activeTab === 'words' || activeTab === 'dictionaries' || activeTab === 'tracking' || activeTab === 'tracking-access' || activeTab === 'tracking-invite' || activeTab === 'shopping' || activeTab === 'shopping-item-form' || activeTab === 'shopping-board-form' || activeTab === 'shopping-board-join' || activeTab === 'shopping-item-history' || activeTab === 'board-form'
// Функция для получения классов скролл-контейнера для каждого таба
// Каждый таб имеет свой изолированный скролл-контейнер для автоматического сохранения позиции скролла
@@ -1418,6 +1418,7 @@ function AppContent() {
onNavigate={handleNavigate}
boardId={tabParams.boardId}
onSaved={() => setWishlistRefreshTrigger(prev => prev + 1)}
isActive={activeTab === 'board-form'}
/>
</div>
</div>
@@ -1473,6 +1474,7 @@ function AppContent() {
onNavigate={handleNavigate}
boardId={tabParams.boardId}
onSaved={() => setShoppingRefreshTrigger(prev => prev + 1)}
isActive={activeTab === 'shopping-board-form'}
/>
</div>
</div>

View File

@@ -1,13 +1,13 @@
import React, { useState, useEffect } from 'react'
import { createPortal } from 'react-dom'
import { useAuth } from './auth/AuthContext'
import BoardMembers from './BoardMembers'
import Toast from './Toast'
import SubmitButton from './SubmitButton'
import DeleteButton from './DeleteButton'
import './Buttons.css'
import './BoardForm.css'
function BoardForm({ boardId, onNavigate, onSaved }) {
function BoardForm({ boardId, onNavigate, onSaved, isActive }) {
const { authFetch } = useAuth()
const [name, setName] = useState('')
const [inviteEnabled, setInviteEnabled] = useState(false)
@@ -252,14 +252,51 @@ function BoardForm({ boardId, onNavigate, onSaved }) {
</>
)}
<div className="form-actions">
<SubmitButton
</div>
{toastMessage && (
<Toast
message={toastMessage.text}
type={toastMessage.type}
onClose={() => setToastMessage(null)}
/>
)}
{isActive ? createPortal(
<div style={{
position: 'fixed',
bottom: 0,
left: 0,
right: 0,
padding: '0.75rem 1rem',
paddingBottom: 'max(0.75rem, env(safe-area-inset-bottom))',
background: 'linear-gradient(to top, white 60%, rgba(255,255,255,0))',
zIndex: 1500,
display: 'flex',
justifyContent: 'center',
gap: '0.75rem',
}}>
<button
onClick={handleSave}
loading={loading}
disabled={!name.trim()}
disabled={loading || isDeleting || !name.trim()}
style={{
flex: 1,
maxWidth: '42rem',
padding: '0.875rem',
background: (loading || !name.trim()) ? undefined : 'linear-gradient(to right, #10b981, #059669)',
backgroundColor: (loading || !name.trim()) ? '#9ca3af' : undefined,
color: 'white',
border: 'none',
borderRadius: '0.5rem',
fontSize: '1rem',
fontWeight: 600,
cursor: (loading || isDeleting || !name.trim()) ? 'not-allowed' : 'pointer',
opacity: loading ? 0.6 : 1,
transition: 'all 0.2s',
}}
>
Сохранить
</SubmitButton>
{loading ? 'Сохранение...' : 'Сохранить'}
</button>
{isEdit && (
<DeleteButton
onClick={handleDelete}
@@ -268,16 +305,9 @@ function BoardForm({ boardId, onNavigate, onSaved }) {
title="Удалить доску"
/>
)}
</div>
</div>
{toastMessage && (
<Toast
message={toastMessage.text}
type={toastMessage.type}
onClose={() => setToastMessage(null)}
/>
)}
</div>,
document.body
) : null}
</div>
)
}

View File

@@ -1,13 +1,13 @@
import React, { useState, useEffect } from 'react'
import { createPortal } from 'react-dom'
import { useAuth } from './auth/AuthContext'
import BoardMembers from './BoardMembers'
import Toast from './Toast'
import SubmitButton from './SubmitButton'
import DeleteButton from './DeleteButton'
import './Buttons.css'
import './BoardForm.css'
function ShoppingBoardForm({ boardId, onNavigate, onSaved }) {
function ShoppingBoardForm({ boardId, onNavigate, onSaved, isActive }) {
const { authFetch } = useAuth()
const [name, setName] = useState('')
const [inviteEnabled, setInviteEnabled] = useState(false)
@@ -245,23 +245,6 @@ function ShoppingBoardForm({ boardId, onNavigate, onSaved }) {
</>
)}
<div className="form-actions">
<SubmitButton
onClick={handleSave}
loading={loading}
disabled={!name.trim()}
>
Сохранить
</SubmitButton>
{isEdit && (
<DeleteButton
onClick={handleDelete}
loading={isDeleting}
disabled={loading}
title="Удалить доску"
/>
)}
</div>
</div>
{toastMessage && (
@@ -271,6 +254,53 @@ function ShoppingBoardForm({ boardId, onNavigate, onSaved }) {
onClose={() => setToastMessage(null)}
/>
)}
{isActive ? createPortal(
<div style={{
position: 'fixed',
bottom: 0,
left: 0,
right: 0,
padding: '0.75rem 1rem',
paddingBottom: 'max(0.75rem, env(safe-area-inset-bottom))',
background: 'linear-gradient(to top, white 60%, rgba(255,255,255,0))',
zIndex: 1500,
display: 'flex',
justifyContent: 'center',
gap: '0.75rem',
}}>
<button
onClick={handleSave}
disabled={loading || isDeleting || !name.trim()}
style={{
flex: 1,
maxWidth: '42rem',
padding: '0.875rem',
background: (loading || !name.trim()) ? undefined : 'linear-gradient(to right, #10b981, #059669)',
backgroundColor: (loading || !name.trim()) ? '#9ca3af' : undefined,
color: 'white',
border: 'none',
borderRadius: '0.5rem',
fontSize: '1rem',
fontWeight: 600,
cursor: (loading || isDeleting || !name.trim()) ? 'not-allowed' : 'pointer',
opacity: loading ? 0.6 : 1,
transition: 'all 0.2s',
}}
>
{loading ? 'Сохранение...' : 'Сохранить'}
</button>
{isEdit && (
<DeleteButton
onClick={handleDelete}
loading={isDeleting}
disabled={loading}
title="Удалить доску"
/>
)}
</div>,
document.body
) : null}
</div>
)
}