6.9.0: Задачи-закупки
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m20s

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
poignatov
2026-03-10 22:37:03 +03:00
parent 786a03bf86
commit 636f53eb04
12 changed files with 1363 additions and 21 deletions

View File

@@ -19,6 +19,7 @@ import ShoppingItemForm from './components/ShoppingItemForm'
import ShoppingBoardForm from './components/ShoppingBoardForm'
import ShoppingBoardJoinPreview from './components/ShoppingBoardJoinPreview'
import ShoppingItemHistory from './components/ShoppingItemHistory'
import PurchaseScreen from './components/PurchaseScreen'
import TodoistIntegration from './components/TodoistIntegration'
import TelegramIntegration from './components/TelegramIntegration'
import FitbitIntegration from './components/FitbitIntegration'
@@ -35,7 +36,7 @@ const FULL_STATISTICS_API_URL = '/d2dc349a-0d13-49b2-a8f0-1ab094bfba9b'
// Определяем основные табы (без крестика) и глубокие табы (с крестиком)
const mainTabs = ['current', 'tasks', 'wishlist', 'shopping', 'profile']
const deepTabs = ['add-words', 'test', 'task-form', 'wishlist-form', 'wishlist-detail', 'board-form', 'board-join', 'words', 'dictionaries', 'todoist-integration', 'telegram-integration', 'fitbit-integration', 'full', 'priorities', 'tracking', 'tracking-access', 'tracking-invite', 'shopping-item-form', 'shopping-board-form', 'shopping-board-join', 'shopping-item-history']
const deepTabs = ['add-words', 'test', 'purchase', 'task-form', 'wishlist-form', 'wishlist-detail', 'board-form', 'board-join', 'words', 'dictionaries', 'todoist-integration', 'telegram-integration', 'fitbit-integration', 'full', 'priorities', 'tracking', 'tracking-access', 'tracking-invite', 'shopping-item-form', 'shopping-board-form', 'shopping-board-join', 'shopping-item-history']
/**
* Гарантирует базовую запись истории для главного экрана перед глубоким табом.
@@ -87,6 +88,7 @@ function AppContent() {
'shopping-board-form': false,
'shopping-board-join': false,
'shopping-item-history': false,
purchase: false,
})
// Отслеживаем, какие табы уже были загружены (для предотвращения повторных загрузок)
@@ -117,6 +119,7 @@ function AppContent() {
'shopping-board-form': false,
'shopping-board-join': false,
'shopping-item-history': false,
purchase: false,
})
// Параметры для навигации между вкладками
@@ -295,7 +298,7 @@ function AppContent() {
// Проверяем URL только для глубоких табов
const tabFromUrl = urlParams.get('tab')
const validTabs = ['current', 'priorities', 'full', 'words', 'add-words', 'dictionaries', 'test', 'tasks', 'task-form', 'wishlist', 'wishlist-form', 'wishlist-detail', 'board-form', 'board-join', 'profile', 'todoist-integration', 'telegram-integration', 'fitbit-integration', 'tracking', 'tracking-access', 'tracking-invite', 'shopping', 'shopping-item-form', 'shopping-board-form', 'shopping-board-join', 'shopping-item-history']
const validTabs = ['current', 'priorities', 'full', 'words', 'add-words', 'dictionaries', 'test', 'purchase', 'tasks', 'task-form', 'wishlist', 'wishlist-form', 'wishlist-detail', 'board-form', 'board-join', 'profile', 'todoist-integration', 'telegram-integration', 'fitbit-integration', 'tracking', 'tracking-access', 'tracking-invite', 'shopping', 'shopping-item-form', 'shopping-board-form', 'shopping-board-join', 'shopping-item-history']
if (tabFromUrl && validTabs.includes(tabFromUrl) && deepTabs.includes(tabFromUrl) && window.history.length > 1) {
// Восстанавливаем глубокий таб из URL только если есть история (не рестарт PWA)
@@ -792,7 +795,7 @@ function AppContent() {
return
}
const validTabs = ['current', 'priorities', 'full', 'words', 'add-words', 'dictionaries', 'test', 'tasks', 'task-form', 'wishlist', 'wishlist-form', 'wishlist-detail', 'profile', 'todoist-integration', 'telegram-integration', 'tracking', 'tracking-access', 'tracking-invite', 'shopping', 'shopping-item-form', 'shopping-board-form', 'shopping-board-join', 'shopping-item-history']
const validTabs = ['current', 'priorities', 'full', 'words', 'add-words', 'dictionaries', 'test', 'purchase', 'tasks', 'task-form', 'wishlist', 'wishlist-form', 'wishlist-detail', 'profile', 'todoist-integration', 'telegram-integration', 'tracking', 'tracking-access', 'tracking-invite', 'shopping', 'shopping-item-form', 'shopping-board-form', 'shopping-board-join', 'shopping-item-history']
// Проверяем state текущей записи истории (куда мы вернулись)
if (event.state && event.state.tab) {
@@ -909,7 +912,7 @@ function AppContent() {
{
// Для task-form и wishlist-form явно удаляем параметры, только если нет никаких параметров
// task-form может иметь taskId (редактирование), wishlistId (создание из желания), returnTo (возврат после создания), или isTest (создание теста)
const isTaskFormWithNoParams = tab === 'task-form' && params.taskId === undefined && params.wishlistId === undefined && params.returnTo === undefined && params.isTest === undefined
const isTaskFormWithNoParams = tab === 'task-form' && params.taskId === undefined && params.wishlistId === undefined && params.returnTo === undefined && params.isTest === undefined && params.isPurchase === undefined
// Проверяем, что boardId не null и не undefined (null означает "нет доски", но это валидное значение)
const hasBoardId = params.boardId !== null && params.boardId !== undefined
const isWishlistFormWithNoParams = tab === 'wishlist-form' && params.wishlistId === undefined && params.newTaskId === undefined && !hasBoardId
@@ -976,7 +979,7 @@ function AppContent() {
}
// Обновляем список задач при возврате из экрана редактирования или теста
// Используем фоновую загрузку, чтобы не показывать индикатор загрузки
if ((activeTab === 'task-form' || activeTab === 'test') && tab === 'tasks') {
if ((activeTab === 'task-form' || activeTab === 'test' || activeTab === 'purchase') && tab === 'tasks') {
fetchTasksData(true)
}
// Сохраняем предыдущий таб при открытии wishlist-form или wishlist-detail
@@ -1037,6 +1040,11 @@ function AppContent() {
handleNavigate('task-form', { taskId: undefined, isTest: true })
}
const handleAddPurchase = () => {
setShowAddModal(false)
handleNavigate('task-form', { taskId: undefined, isPurchase: true })
}
// Обработчик навигации для компонентов
const handleNavigate = (tab, params = {}, options = {}) => {
handleTabChange(tab, params, options)
@@ -1116,7 +1124,7 @@ function AppContent() {
}
// Определяем, нужно ли скрывать нижнюю панель (для fullscreen экранов)
const isFullscreenTab = activeTab === 'test' || 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-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-item-form' || activeTab === 'shopping-board-form' || activeTab === 'shopping-board-join' || activeTab === 'shopping-item-history'
// Функция для получения классов скролл-контейнера для каждого таба
// Каждый таб имеет свой изолированный скролл-контейнер для автоматического сохранения позиции скролла
@@ -1145,7 +1153,7 @@ function AppContent() {
if (tabName === 'current') {
return 'max-w-7xl mx-auto p-4 md:p-6'
}
if (tabName === 'full' || tabName === 'priorities' || tabName === 'dictionaries' || tabName === 'words' || tabName === 'shopping-item-history') {
if (tabName === 'full' || tabName === 'priorities' || tabName === 'dictionaries' || tabName === 'words' || tabName === 'shopping-item-history' || tabName === 'purchase') {
return 'max-w-7xl mx-auto px-4 md:px-8 py-0'
}
// Fullscreen табы без отступов
@@ -1257,7 +1265,7 @@ function AppContent() {
{loadedTabs.test && (
<div className={getTabContainerClasses('test')}>
<div className={getInnerContainerClasses('test')}>
<TestWords
<TestWords
onNavigate={handleNavigate}
wordCount={tabParams.wordCount}
configId={tabParams.configId}
@@ -1268,6 +1276,19 @@ function AppContent() {
</div>
)}
{loadedTabs.purchase && (
<div className={getTabContainerClasses('purchase')}>
<div className={getInnerContainerClasses('purchase')}>
<PurchaseScreen
onNavigate={handleNavigate}
purchaseConfigId={tabParams.purchaseConfigId}
taskId={tabParams.taskId}
taskName={tabParams.taskName}
/>
</div>
</div>
)}
{loadedTabs.tasks && (
<div className={getTabContainerClasses('tasks')}>
<div className={getInnerContainerClasses('tasks')}>
@@ -1293,6 +1314,7 @@ function AppContent() {
taskId={tabParams.taskId}
wishlistId={tabParams.wishlistId}
isTest={tabParams.isTest}
isPurchase={tabParams.isPurchase}
returnTo={tabParams.returnTo}
returnWishlistId={tabParams.returnWishlistId}
/>
@@ -1787,6 +1809,17 @@ function AppContent() {
</svg>
Тест
</button>
<button
className="task-add-modal-button task-add-modal-button-purchase"
onClick={handleAddPurchase}
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="9" cy="21" r="1"></circle>
<circle cx="20" cy="21" r="1"></circle>
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
</svg>
Закупка
</button>
</div>
</div>
</div>