diff --git a/VERSION b/VERSION
index cf79bf9..1de66e5 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-6.10.0
+6.11.0
diff --git a/play-life-web/package.json b/play-life-web/package.json
index 0ab6acc..bbba9bb 100644
--- a/play-life-web/package.json
+++ b/play-life-web/package.json
@@ -1,6 +1,6 @@
{
"name": "play-life-web",
- "version": "6.10.0",
+ "version": "6.11.0",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/play-life-web/src/App.jsx b/play-life-web/src/App.jsx
index 289093e..8bbbc3e 100644
--- a/play-life-web/src/App.jsx
+++ b/play-life-web/src/App.jsx
@@ -131,9 +131,6 @@ function AppContent() {
// Счётчик для сброса формы товара при каждом открытии
const [shoppingItemFormKey, setShoppingItemFormKey] = useState(0)
- // Модальное окно выбора типа задачи
- const [showAddModal, setShowAddModal] = useState(false)
-
// Ref для функции открытия модала добавления записи в CurrentWeek
const currentWeekAddModalRef = useRef(null)
@@ -334,9 +331,12 @@ function AppContent() {
if (savedTab && validTabs.includes(savedTab) && mainTabs.includes(savedTab)) {
setActiveTab(savedTab)
setLoadedTabs(prev => ({ ...prev, [savedTab]: true }))
+ // Сохраняем таб в history state для корректной работы кнопки "назад"
+ window.history.replaceState({ tab: savedTab }, '', window.location.href)
} else {
// Если нет сохранённого таба — активируем current по умолчанию
setLoadedTabs(prev => ({ ...prev, current: true }))
+ window.history.replaceState({ tab: 'current' }, '', window.location.href)
}
// Очищаем URL от параметров таба, если это основной таб
if (tabFromUrl && mainTabs.includes(tabFromUrl)) {
@@ -847,9 +847,9 @@ function AppContent() {
setSelectedProject(null)
clearUrl(event.state.tab)
} else {
- // Если state пустой, используем сохраненный таб из localStorage
+ // Если state пустой, используем сохраненный таб из localStorage (только основные табы)
const savedTab = window.localStorage?.getItem('activeTab')
- const validMainTab = savedTab && validTabs.includes(savedTab) ? savedTab : 'current'
+ const validMainTab = savedTab && mainTabs.includes(savedTab) ? savedTab : 'current'
setActiveTab(validMainTab)
setTabParams({})
markTabAsLoaded(validMainTab)
@@ -911,8 +911,8 @@ 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 && params.isPurchase === undefined
+ // task-form может иметь taskId (редактирование), wishlistId (создание из желания), returnTo (возврат после создания)
+ const isTaskFormWithNoParams = tab === 'task-form' && params.taskId === undefined && params.wishlistId === undefined && params.returnTo === 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
@@ -1025,24 +1025,9 @@ function AppContent() {
}
}
- // Обработчики для кнопки добавления задачи
+ // Обработчик для кнопки добавления задачи
const handleAddClick = () => {
- setShowAddModal(true)
- }
-
- const handleAddTask = () => {
- setShowAddModal(false)
- handleNavigate('task-form', { taskId: undefined, isTest: false })
- }
-
- const handleAddTest = () => {
- setShowAddModal(false)
- handleNavigate('task-form', { taskId: undefined, isTest: true })
- }
-
- const handleAddPurchase = () => {
- setShowAddModal(false)
- handleNavigate('task-form', { taskId: undefined, isPurchase: true })
+ handleNavigate('task-form', { taskId: undefined })
}
// Обработчик навигации для компонентов
@@ -1308,13 +1293,11 @@ function AppContent() {
{loadedTabs['task-form'] && (
-
@@ -1779,51 +1762,6 @@ function AppContent() {
)}
- {/* Модальное окно выбора типа задачи */}
- {showAddModal && (
-
setShowAddModal(false)}>
-
e.stopPropagation()}>
-
-
Что добавить?
-
-
-
-
-
-
-
-
- )}
)
}
diff --git a/play-life-web/src/components/TaskForm.css b/play-life-web/src/components/TaskForm.css
index 233e0ec..9c4bff8 100644
--- a/play-life-web/src/components/TaskForm.css
+++ b/play-life-web/src/components/TaskForm.css
@@ -452,6 +452,57 @@
color: #ef4444;
}
+/* Task type tabs */
+.task-type-tabs-section {
+ background: #f0f9ff;
+ border: 1px solid #bae6fd;
+ border-radius: 0.5rem;
+ padding: 0;
+ overflow: hidden;
+}
+
+.task-type-tabs {
+ display: flex;
+ border-bottom: 1px solid #bae6fd;
+}
+
+.task-type-tab {
+ flex: 1;
+ padding: 0.625rem 0;
+ border: none;
+ background: transparent;
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: #64748b;
+ cursor: pointer;
+ transition: all 0.15s ease;
+ position: relative;
+}
+
+.task-type-tab:not(:last-child) {
+ border-right: 1px solid #bae6fd;
+}
+
+.task-type-tab-active {
+ color: #3498db;
+ font-weight: 600;
+ background: rgba(52, 152, 219, 0.08);
+}
+
+.task-type-tab-active::after {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 2px;
+ background: #3498db;
+}
+
+.task-type-content {
+ padding: 1rem;
+}
+
/* Test configuration styles */
.test-config-section {
background: #f0f9ff;
diff --git a/play-life-web/src/components/TaskForm.jsx b/play-life-web/src/components/TaskForm.jsx
index 01b77cc..2a96259 100644
--- a/play-life-web/src/components/TaskForm.jsx
+++ b/play-life-web/src/components/TaskForm.jsx
@@ -8,7 +8,7 @@ import './TaskForm.css'
const API_URL = '/api/tasks'
const PROJECTS_API_URL = '/projects'
-function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = false, isPurchase: isPurchaseFromProps = false, returnTo, returnWishlistId }) {
+function TaskForm({ onNavigate, taskId, wishlistId, returnTo, returnWishlistId }) {
const { authFetch } = useAuth()
const [name, setName] = useState('')
const [progressionBase, setProgressionBase] = useState('')
@@ -30,13 +30,13 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
const [currentWishlistId, setCurrentWishlistId] = useState(null) // Текущий wishlist_id задачи
const [rewardPolicy, setRewardPolicy] = useState('personal') // Политика награждения: 'personal' или 'general'
// Test-specific state
- const [isTest, setIsTest] = useState(isTestFromProps)
+ const [isTest, setIsTest] = useState(false)
const [wordsCount, setWordsCount] = useState('10')
const [maxCards, setMaxCards] = useState('')
const [selectedDictionaryIDs, setSelectedDictionaryIDs] = useState([])
const [availableDictionaries, setAvailableDictionaries] = useState([])
// Purchase-specific state
- const [isPurchase, setIsPurchase] = useState(isPurchaseFromProps)
+ const [isPurchase, setIsPurchase] = useState(false)
const [availableBoards, setAvailableBoards] = useState([])
const [selectedPurchaseBoards, setSelectedPurchaseBoards] = useState([])
const debounceTimer = useRef(null)
@@ -119,12 +119,12 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
setError('')
setLoadingTask(false)
// Reset test-specific fields
- setIsTest(isTestFromProps)
+ setIsTest(false)
setWordsCount('10')
setMaxCards('')
setSelectedDictionaryIDs([])
// Reset purchase-specific fields
- setIsPurchase(isPurchaseFromProps)
+ setIsPurchase(false)
setSelectedPurchaseBoards([])
if (debounceTimer.current) {
clearTimeout(debounceTimer.current)
@@ -446,19 +446,8 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
}
}
- // Очистка подзадач при переключении задачи в режим теста
- useEffect(() => {
- if (isTest && subtasks.length > 0) {
- setSubtasks([])
- }
- }, [isTest])
-
- // Очистка подзадач при переключении задачи в режим закупки
- useEffect(() => {
- if (isPurchase && subtasks.length > 0) {
- setSubtasks([])
- }
- }, [isPurchase])
+ // Подзадачи, словари и товары сохраняются в памяти при переключении типа.
+ // При сохранении используются только данные текущего активного типа.
// Пересчет rewards при изменении reward_message (debounce)
useEffect(() => {
@@ -745,8 +734,8 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
const payload = {
name: name.trim(),
reward_message: rewardMessage.trim() || null,
- // Тесты и задачи с желанием не могут иметь прогрессию
- progression_base: (isLinkedToWishlist || isTest) ? null : (progressionBase ? parseFloat(progressionBase) : null),
+ // Тесты, закупки и задачи с желанием не могут иметь прогрессию
+ progression_base: (isLinkedToWishlist || isTest || isPurchase) ? null : (progressionBase ? parseFloat(progressionBase) : null),
repetition_period: repetitionPeriod,
repetition_date: repetitionDate,
// При создании: отправляем currentWishlistId если указан (уже число)
@@ -763,7 +752,7 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
position: r.position,
project_name: r.project_name.trim(),
value: parseFloat(r.value) || 0,
- use_progression: !!(progressionBase && r.use_progression)
+ use_progression: !!(progressionBase && !isTest && !isPurchase && r.use_progression)
})),
subtasks: (isTest || isPurchase) ? [] : subtasks.map((st, index) => ({
id: st.id || undefined,
@@ -774,7 +763,7 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
position: r.position,
project_name: r.project_name.trim(),
value: parseFloat(r.value) || 0,
- use_progression: !!(progressionBase && r.use_progression)
+ use_progression: !!(progressionBase && !isTest && !isPurchase && r.use_progression)
}))
})),
// Test-specific fields
@@ -828,12 +817,16 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
console.log('[TaskForm] Saving newTaskId to sessionStorage and going back:', newTaskId)
// Сохраняем newTaskId в sessionStorage, чтобы WishlistForm мог его прочитать
sessionStorage.setItem('wishlistFormNewTaskId', String(newTaskId))
- // Используем history.back() чтобы не создавать лишнюю запись в стеке
window.history.back()
} else {
console.log('[TaskForm] No returnTo, going back in history')
- // Возврат назад по стеку истории (на список задач, желаний и т.д.)
- window.history.back()
+ // Возвращаемся назад, если есть предыдущая запись
+ const state = window.history.state
+ if ((state && state.previousTab) || window.history.length > 1) {
+ window.history.back()
+ } else {
+ onNavigate('tasks')
+ }
}
} catch (err) {
setToastMessage({ text: err.message || 'Ошибка при сохранении задачи', type: 'error' })
@@ -852,7 +845,17 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
const handleCancel = () => {
resetForm()
- window.history.back()
+ // Проверяем, есть ли предыдущая запись в стеке для history.back()
+ const state = window.history.state
+ if (state && state.previousTab) {
+ // Есть предыдущая запись — можно безопасно вернуться
+ window.history.back()
+ } else if (window.history.length > 1) {
+ window.history.back()
+ } else {
+ // Стек пуст — прямой переход
+ onNavigate('tasks')
+ }
}
const handleDelete = async () => {
@@ -920,10 +923,36 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
/>
- {/* Subtask-specific fields (regular task) */}
- {!isTest && !isPurchase && !wishlistInfo && (
-
-
+ {/* Task type tabs */}
+ {!wishlistInfo && (
+
+
+
+
+
+
+
+ {/* Задача */}
+ {!isTest && !isPurchase && (
+
- {progressionBase && (
+ {progressionBase && !isTest && !isPurchase && (
+
+ )}
+
+ {/* Тест */}
+ {isTest && (
+
+
+
+
+
+ {availableDictionaries.map(dict => (
+
+ ))}
+ {availableDictionaries.length === 0 && (
+
+ Нет доступных словарей. Создайте словарь в разделе "Словари".
+
+ )}
+
+
+
+ )}
+
+ {/* Закупка */}
+ {isPurchase && (
+
+
+
+
+ {availableBoards.map(board => (
+
+ ))}
+ {availableBoards.length === 0 && (
+
+ Нет доступных досок. Создайте доску в разделе "Товары".
+
+ )}
+
+
+
+ )}
)}
@@ -1090,128 +1239,6 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
)}
- {/* Test-specific fields */}
- {isTest && (
-
-
-
-
-
-
- {availableDictionaries.map(dict => (
-
- ))}
- {availableDictionaries.length === 0 && (
-
- Нет доступных словарей. Создайте словарь в разделе "Словари".
-
- )}
-
-
-
- )}
-
- {/* Purchase-specific fields */}
- {isPurchase && (
-
-
-
-
-
- {availableBoards.map(board => (
-
- ))}
- {availableBoards.length === 0 && (
-
- Нет доступных досок. Создайте доску в разделе "Товары".
-
- )}
-
-
-
- )}
-
{!wishlistInfo && (
@@ -1338,7 +1365,7 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
placeholder="Score"
className="form-input reward-score-input"
/>
- {progressionBase && (
+ {progressionBase && !isTest && !isPurchase && (