Добавлена связь задач с желаниями
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 58s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 58s
This commit is contained in:
@@ -6,7 +6,7 @@ import './TaskForm.css'
|
||||
const API_URL = '/api/tasks'
|
||||
const PROJECTS_API_URL = '/projects'
|
||||
|
||||
function TaskForm({ onNavigate, taskId }) {
|
||||
function TaskForm({ onNavigate, taskId, wishlistId }) {
|
||||
const { authFetch } = useAuth()
|
||||
const [name, setName] = useState('')
|
||||
const [progressionBase, setProgressionBase] = useState('')
|
||||
@@ -22,6 +22,8 @@ function TaskForm({ onNavigate, taskId }) {
|
||||
const [toastMessage, setToastMessage] = useState(null)
|
||||
const [loadingTask, setLoadingTask] = useState(false)
|
||||
const [isDeleting, setIsDeleting] = useState(false)
|
||||
const [wishlistInfo, setWishlistInfo] = useState(null) // Информация о связанном желании
|
||||
const [currentWishlistId, setCurrentWishlistId] = useState(null) // Текущий wishlist_id задачи
|
||||
const debounceTimer = useRef(null)
|
||||
|
||||
// Загрузка проектов для автокомплита
|
||||
@@ -65,8 +67,37 @@ function TaskForm({ onNavigate, taskId }) {
|
||||
} else {
|
||||
// Сбрасываем форму при создании новой задачи
|
||||
resetForm()
|
||||
if (wishlistId) {
|
||||
// Преобразуем wishlistId в число
|
||||
const wishlistIdNum = typeof wishlistId === 'string' ? parseInt(wishlistId, 10) : wishlistId
|
||||
setCurrentWishlistId(wishlistIdNum)
|
||||
// Загружаем данные желания здесь, чтобы они не сбросились
|
||||
const loadWishlistData = async () => {
|
||||
try {
|
||||
const response = await authFetch(`/api/wishlist/${wishlistIdNum}`)
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
setWishlistInfo({ id: data.id, name: data.name })
|
||||
// Предзаполняем название задачи названием желания
|
||||
if (data.name) {
|
||||
setName(data.name)
|
||||
}
|
||||
// Предзаполняем сообщение награды
|
||||
if (data.name) {
|
||||
setRewardMessage(`Выполнить желание: ${data.name}`)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error loading wishlist:', err)
|
||||
}
|
||||
}
|
||||
loadWishlistData()
|
||||
} else {
|
||||
setCurrentWishlistId(null)
|
||||
setWishlistInfo(null)
|
||||
}
|
||||
}
|
||||
}, [taskId])
|
||||
}, [taskId, wishlistId, authFetch])
|
||||
|
||||
const loadTask = async () => {
|
||||
setLoadingTask(true)
|
||||
@@ -263,6 +294,28 @@ function TaskForm({ onNavigate, taskId }) {
|
||||
use_progression: r.use_progression
|
||||
}))
|
||||
})))
|
||||
|
||||
// Загружаем информацию о связанном желании, если есть
|
||||
if (data.task.wishlist_id) {
|
||||
setCurrentWishlistId(data.task.wishlist_id)
|
||||
try {
|
||||
const wishlistResponse = await authFetch(`/api/wishlist/${data.task.wishlist_id}`)
|
||||
if (wishlistResponse.ok) {
|
||||
const wishlistData = await wishlistResponse.json()
|
||||
setWishlistInfo({ id: wishlistData.id, name: wishlistData.name })
|
||||
// Если задача привязана к желанию, очищаем поля повторения и прогрессии
|
||||
setRepetitionPeriodValue('')
|
||||
setRepetitionPeriodType('day')
|
||||
setRepetitionMode('after')
|
||||
setProgressionBase('')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error loading wishlist info:', err)
|
||||
}
|
||||
} else {
|
||||
setCurrentWishlistId(null)
|
||||
setWishlistInfo(null)
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err.message)
|
||||
} finally {
|
||||
@@ -422,12 +475,31 @@ function TaskForm({ onNavigate, taskId }) {
|
||||
}
|
||||
}
|
||||
|
||||
// Проверяем, что задача с привязанным желанием не может быть периодической
|
||||
const isLinkedToWishlist = wishlistInfo !== null || (taskId && currentWishlistId)
|
||||
if (isLinkedToWishlist && repetitionPeriodValue && repetitionPeriodValue.trim() !== '') {
|
||||
const value = parseInt(repetitionPeriodValue.trim(), 10)
|
||||
if (!isNaN(value) && value !== 0) {
|
||||
setError('Задачи, привязанные к желанию, не могут быть периодическими')
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Проверяем, что задача с привязанным желанием не может иметь прогрессию
|
||||
if (isLinkedToWishlist && progressionBase && progressionBase.trim() !== '') {
|
||||
setError('Задачи, привязанные к желанию, не могут иметь прогрессию')
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// Преобразуем период повторения в строку INTERVAL для PostgreSQL или repetition_date
|
||||
let repetitionPeriod = null
|
||||
let repetitionDate = null
|
||||
|
||||
if (repetitionPeriodValue && repetitionPeriodValue.trim() !== '') {
|
||||
// Если задача привязана к желанию, не устанавливаем повторения
|
||||
if (!isLinkedToWishlist && repetitionPeriodValue && repetitionPeriodValue.trim() !== '') {
|
||||
const valueStr = repetitionPeriodValue.trim()
|
||||
const value = parseInt(valueStr, 10)
|
||||
|
||||
@@ -482,9 +554,16 @@ function TaskForm({ onNavigate, taskId }) {
|
||||
const payload = {
|
||||
name: name.trim(),
|
||||
reward_message: rewardMessage.trim() || null,
|
||||
progression_base: progressionBase ? parseFloat(progressionBase) : null,
|
||||
// Если задача привязана к желанию, не отправляем progression_base
|
||||
progression_base: isLinkedToWishlist ? null : (progressionBase ? parseFloat(progressionBase) : null),
|
||||
repetition_period: repetitionPeriod,
|
||||
repetition_date: repetitionDate,
|
||||
// При создании: отправляем currentWishlistId если указан (уже число)
|
||||
// При редактировании: отправляем null только если была привязка (currentWishlistId) и пользователь отвязал (!wishlistInfo)
|
||||
// Если не было привязки или привязка осталась - не отправляем поле (undefined)
|
||||
wishlist_id: taskId
|
||||
? (currentWishlistId && !wishlistInfo ? null : undefined)
|
||||
: (currentWishlistId || undefined),
|
||||
rewards: rewards.map(r => ({
|
||||
position: r.position,
|
||||
project_name: r.project_name.trim(),
|
||||
@@ -543,6 +622,13 @@ function TaskForm({ onNavigate, taskId }) {
|
||||
}
|
||||
}
|
||||
|
||||
const handleUnlinkWishlist = () => {
|
||||
if (window.confirm('Отвязать задачу от желания?')) {
|
||||
setCurrentWishlistId(null)
|
||||
setWishlistInfo(null)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
resetForm()
|
||||
onNavigate?.('tasks')
|
||||
@@ -608,6 +694,27 @@ function TaskForm({ onNavigate, taskId }) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Информация о связанном желании */}
|
||||
{wishlistInfo && (
|
||||
<div className="form-group">
|
||||
<div className="wishlist-link-info">
|
||||
<span className="wishlist-link-text">
|
||||
Связана с желанием: <strong>{wishlistInfo.name}</strong>
|
||||
</span>
|
||||
{taskId && currentWishlistId && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleUnlinkWishlist}
|
||||
className="wishlist-unlink-x"
|
||||
title="Отвязать от желания"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="form-group">
|
||||
<label htmlFor="progression_base">Прогрессия</label>
|
||||
<input
|
||||
@@ -615,18 +722,24 @@ function TaskForm({ onNavigate, taskId }) {
|
||||
type="number"
|
||||
step="any"
|
||||
value={progressionBase}
|
||||
onChange={(e) => setProgressionBase(e.target.value)}
|
||||
onChange={(e) => {
|
||||
if (!wishlistInfo) {
|
||||
setProgressionBase(e.target.value)
|
||||
}
|
||||
}}
|
||||
placeholder="Базовое значение"
|
||||
className="form-input"
|
||||
disabled={wishlistInfo !== null}
|
||||
/>
|
||||
<small style={{ color: '#666', fontSize: '0.9em' }}>
|
||||
Оставьте пустым, если прогрессия не используется
|
||||
<small style={{ color: wishlistInfo ? '#e74c3c' : '#666', fontSize: '0.9em' }}>
|
||||
{wishlistInfo ? 'Задачи, привязанные к желанию, не могут иметь прогрессию' : 'Оставьте пустым, если прогрессия не используется'}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label htmlFor="repetition_period">Повторения</label>
|
||||
{(() => {
|
||||
const isLinkedToWishlist = wishlistInfo !== null
|
||||
const hasValidValue = repetitionPeriodValue && repetitionPeriodValue.trim() !== '' && parseInt(repetitionPeriodValue.trim(), 10) !== 0
|
||||
const isEachMode = hasValidValue && repetitionMode === 'each'
|
||||
const isYearType = isEachMode && repetitionPeriodType === 'year'
|
||||
@@ -634,7 +747,7 @@ function TaskForm({ onNavigate, taskId }) {
|
||||
return (
|
||||
<>
|
||||
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
||||
{hasValidValue && (
|
||||
{hasValidValue && !isLinkedToWishlist && (
|
||||
<select
|
||||
value={repetitionMode}
|
||||
onChange={(e) => {
|
||||
@@ -659,12 +772,17 @@ function TaskForm({ onNavigate, taskId }) {
|
||||
type={isYearType ? 'text' : 'number'}
|
||||
min="0"
|
||||
value={repetitionPeriodValue}
|
||||
onChange={(e) => setRepetitionPeriodValue(e.target.value)}
|
||||
onChange={(e) => {
|
||||
if (!isLinkedToWishlist) {
|
||||
setRepetitionPeriodValue(e.target.value)
|
||||
}
|
||||
}}
|
||||
placeholder={isYearType ? 'ММ-ДД' : 'Число'}
|
||||
className="form-input"
|
||||
style={{ flex: '1' }}
|
||||
disabled={isLinkedToWishlist}
|
||||
/>
|
||||
{hasValidValue && (
|
||||
{hasValidValue && !isLinkedToWishlist && (
|
||||
<select
|
||||
value={repetitionPeriodType}
|
||||
onChange={(e) => setRepetitionPeriodType(e.target.value)}
|
||||
@@ -691,7 +809,9 @@ function TaskForm({ onNavigate, taskId }) {
|
||||
)}
|
||||
</div>
|
||||
<small style={{ color: '#666', fontSize: '0.9em' }}>
|
||||
{isEachMode ? (
|
||||
{isLinkedToWishlist ? (
|
||||
<span style={{ color: '#e74c3c' }}>Задачи, привязанные к желанию, не могут быть периодическими</span>
|
||||
) : isEachMode ? (
|
||||
repetitionPeriodType === 'week' ? 'Номер дня недели (1-7, где 1 = понедельник)' :
|
||||
repetitionPeriodType === 'month' ? 'Номер дня месяца (1-31)' :
|
||||
'Дата в формате ММ-ДД (например, 02-01 для 1 февраля)'
|
||||
|
||||
Reference in New Issue
Block a user