6.10.0: Синий блок настроек для задач
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m6s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m6s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -227,23 +227,24 @@
|
||||
}
|
||||
|
||||
.add-subtask-button {
|
||||
padding: 0.375rem;
|
||||
background: #6366f1;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem;
|
||||
background: white;
|
||||
color: #3498db;
|
||||
border: 1px dashed #bae6fd;
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
min-width: 2rem;
|
||||
height: 2rem;
|
||||
width: 100%;
|
||||
height: 2.5rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.add-subtask-button:hover {
|
||||
background: #4f46e5;
|
||||
background: #e0f2fe;
|
||||
border-color: #3498db;
|
||||
}
|
||||
|
||||
.subtask-form-item {
|
||||
@@ -311,9 +312,9 @@
|
||||
}
|
||||
|
||||
.remove-subtask-button {
|
||||
padding: 0.5rem;
|
||||
background: #ef4444;
|
||||
color: white;
|
||||
padding: 0.25rem;
|
||||
background: none;
|
||||
color: #9ca3af;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
@@ -322,12 +323,12 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
min-width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
min-width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.remove-subtask-button:hover {
|
||||
background: #dc2626;
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
@@ -459,13 +460,23 @@
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.test-config-section > label {
|
||||
.test-config-section > label,
|
||||
.test-config-section > .subtasks-header > label {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #3498db;
|
||||
margin-bottom: 1rem !important;
|
||||
}
|
||||
|
||||
.test-config-section > .subtasks-header {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.test-config-section .subtask-form-item {
|
||||
background: white;
|
||||
border-color: #bae6fd;
|
||||
}
|
||||
|
||||
.test-config-fields {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
|
||||
@@ -920,6 +920,148 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Subtask-specific fields (regular task) */}
|
||||
{!isTest && !isPurchase && !wishlistInfo && (
|
||||
<div className="form-group test-config-section">
|
||||
<label>Настройки задачи</label>
|
||||
<div className="test-field-group" style={{ marginBottom: '1rem' }}>
|
||||
<label htmlFor="progression_base">Прогрессия</label>
|
||||
<input
|
||||
id="progression_base"
|
||||
type="number"
|
||||
step="any"
|
||||
value={progressionBase}
|
||||
onChange={(e) => setProgressionBase(e.target.value)}
|
||||
placeholder="Базовое значение"
|
||||
className="form-input"
|
||||
/>
|
||||
<small style={{ color: '#666', fontSize: '0.9em' }}>
|
||||
Оставьте пустым, если прогрессия не используется
|
||||
</small>
|
||||
</div>
|
||||
<label style={{ fontSize: '0.875rem' }}>Подзадачи</label>
|
||||
{subtasks.map((subtask, index) => (
|
||||
<div key={index} className="subtask-form-item">
|
||||
<div className="subtask-header-row">
|
||||
<div className="subtask-position-controls">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleMoveSubtaskUp(index)}
|
||||
className="move-subtask-button"
|
||||
disabled={index === 0}
|
||||
title="Переместить вверх"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<polyline points="18 15 12 9 6 15"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleMoveSubtaskDown(index)}
|
||||
className="move-subtask-button"
|
||||
disabled={index === subtasks.length - 1}
|
||||
title="Переместить вниз"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
value={subtask.name}
|
||||
onChange={(e) => handleSubtaskChange(index, 'name', e.target.value)}
|
||||
placeholder="Название подзадачи"
|
||||
className="form-input subtask-name-input"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveSubtask(index)}
|
||||
className="remove-subtask-button"
|
||||
title="Удалить подзадачу"
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
value={subtask.reward_message}
|
||||
onChange={(e) => handleSubtaskRewardMessageChange(index, e.target.value)}
|
||||
placeholder="Сообщение награды (опционально)"
|
||||
className="form-textarea"
|
||||
rows={2}
|
||||
/>
|
||||
{subtask.rewards && subtask.rewards.length > 0 && (
|
||||
<div className="subtask-rewards">
|
||||
{subtask.rewards.map((reward, rIndex) => {
|
||||
return (
|
||||
<div key={rIndex} className="reward-item">
|
||||
<span className="reward-number">{rIndex}</span>
|
||||
<input
|
||||
type="text"
|
||||
value={reward.project_name}
|
||||
onChange={(e) => {
|
||||
const newSubtasks = [...subtasks]
|
||||
newSubtasks[index].rewards[rIndex].project_name = e.target.value
|
||||
setSubtasks(newSubtasks)
|
||||
}}
|
||||
placeholder="Проект"
|
||||
className="form-input reward-project-input"
|
||||
list={`subtask-projects-${index}-${rIndex}`}
|
||||
/>
|
||||
<datalist id={`subtask-projects-${index}-${rIndex}`}>
|
||||
{projects.map(p => (
|
||||
<option key={p.project_id} value={p.project_name} />
|
||||
))}
|
||||
</datalist>
|
||||
<input
|
||||
type="number"
|
||||
step="any"
|
||||
value={reward.value}
|
||||
onChange={(e) => {
|
||||
const newSubtasks = [...subtasks]
|
||||
newSubtasks[index].rewards[rIndex].value = e.target.value
|
||||
setSubtasks(newSubtasks)
|
||||
}}
|
||||
placeholder="Score"
|
||||
className="form-input reward-score-input"
|
||||
/>
|
||||
{progressionBase && (
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={0}
|
||||
className={`progression-button progression-button-subtask ${reward.use_progression ? 'progression-button-filled' : 'progression-button-outlined'}`}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault()
|
||||
const newSubtasks = [...subtasks]
|
||||
newSubtasks[index].rewards[rIndex].use_progression = !newSubtasks[index].rewards[rIndex].use_progression
|
||||
setSubtasks(newSubtasks)
|
||||
}}
|
||||
title={reward.use_progression ? 'Отключить прогрессию' : 'Включить прогрессию'}
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
|
||||
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<button type="button" onClick={handleAddSubtask} className="add-subtask-button" title="Добавить подзадачу">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<line x1="12" y1="5" x2="12" y2="19"></line>
|
||||
<line x1="5" y1="12" x2="19" y2="12"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Информация о связанном желании */}
|
||||
{wishlistInfo && (
|
||||
<div className="form-group">
|
||||
@@ -939,7 +1081,7 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
|
||||
<option value="general">Общая</option>
|
||||
</select>
|
||||
<small style={{ color: '#666', fontSize: '0.9em', display: 'block', marginTop: '4px' }}>
|
||||
{rewardPolicy === 'personal'
|
||||
{rewardPolicy === 'personal'
|
||||
? 'Задача выполняется только если вы сами завершили желание. Если другой пользователь завершит желание, задача будет удалена.'
|
||||
: 'Задача выполняется если кто-либо (неважно кто) отметил желание завершённым.'}
|
||||
</small>
|
||||
@@ -948,29 +1090,6 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isTest && !wishlistInfo && (
|
||||
<div className="form-group">
|
||||
<label htmlFor="progression_base">Прогрессия</label>
|
||||
<input
|
||||
id="progression_base"
|
||||
type="number"
|
||||
step="any"
|
||||
value={progressionBase}
|
||||
onChange={(e) => {
|
||||
if (!wishlistInfo) {
|
||||
setProgressionBase(e.target.value)
|
||||
}
|
||||
}}
|
||||
placeholder="Базовое значение"
|
||||
className="form-input"
|
||||
disabled={wishlistInfo !== null}
|
||||
/>
|
||||
<small style={{ color: wishlistInfo ? '#e74c3c' : '#666', fontSize: '0.9em' }}>
|
||||
{wishlistInfo ? 'Задачи, привязанные к желанию, не могут иметь прогрессию' : 'Оставьте пустым, если прогрессия не используется'}
|
||||
</small>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Test-specific fields */}
|
||||
{isTest && (
|
||||
<div className="form-group test-config-section">
|
||||
@@ -1242,136 +1361,6 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!isTest && !isPurchase && (
|
||||
<div className="form-group">
|
||||
<div className="subtasks-header">
|
||||
<label>Подзадачи</label>
|
||||
<button type="button" onClick={handleAddSubtask} className="add-subtask-button" title="Добавить подзадачу">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<line x1="12" y1="5" x2="12" y2="19"></line>
|
||||
<line x1="5" y1="12" x2="19" y2="12"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{subtasks.map((subtask, index) => (
|
||||
<div key={index} className="subtask-form-item">
|
||||
<div className="subtask-header-row">
|
||||
<div className="subtask-position-controls">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleMoveSubtaskUp(index)}
|
||||
className="move-subtask-button"
|
||||
disabled={index === 0}
|
||||
title="Переместить вверх"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<polyline points="18 15 12 9 6 15"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleMoveSubtaskDown(index)}
|
||||
className="move-subtask-button"
|
||||
disabled={index === subtasks.length - 1}
|
||||
title="Переместить вниз"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
value={subtask.name}
|
||||
onChange={(e) => handleSubtaskChange(index, 'name', e.target.value)}
|
||||
placeholder="Название подзадачи"
|
||||
className="form-input subtask-name-input"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveSubtask(index)}
|
||||
className="remove-subtask-button"
|
||||
title="Удалить подзадачу"
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M3 6h18"></path>
|
||||
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path>
|
||||
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path>
|
||||
<line x1="10" y1="11" x2="10" y2="17"></line>
|
||||
<line x1="14" y1="11" x2="14" y2="17"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
value={subtask.reward_message}
|
||||
onChange={(e) => handleSubtaskRewardMessageChange(index, e.target.value)}
|
||||
placeholder="Сообщение награды (опционально)"
|
||||
className="form-textarea"
|
||||
rows={2}
|
||||
/>
|
||||
{subtask.rewards && subtask.rewards.length > 0 && (
|
||||
<div className="subtask-rewards">
|
||||
{subtask.rewards.map((reward, rIndex) => {
|
||||
return (
|
||||
<div key={rIndex} className="reward-item">
|
||||
<span className="reward-number">{rIndex}</span>
|
||||
<input
|
||||
type="text"
|
||||
value={reward.project_name}
|
||||
onChange={(e) => {
|
||||
const newSubtasks = [...subtasks]
|
||||
newSubtasks[index].rewards[rIndex].project_name = e.target.value
|
||||
setSubtasks(newSubtasks)
|
||||
}}
|
||||
placeholder="Проект"
|
||||
className="form-input reward-project-input"
|
||||
list={`subtask-projects-${index}-${rIndex}`}
|
||||
/>
|
||||
<datalist id={`subtask-projects-${index}-${rIndex}`}>
|
||||
{projects.map(p => (
|
||||
<option key={p.project_id} value={p.project_name} />
|
||||
))}
|
||||
</datalist>
|
||||
<input
|
||||
type="number"
|
||||
step="any"
|
||||
value={reward.value}
|
||||
onChange={(e) => {
|
||||
const newSubtasks = [...subtasks]
|
||||
newSubtasks[index].rewards[rIndex].value = e.target.value
|
||||
setSubtasks(newSubtasks)
|
||||
}}
|
||||
placeholder="Score"
|
||||
className="form-input reward-score-input"
|
||||
/>
|
||||
{progressionBase && (
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={0}
|
||||
className={`progression-button progression-button-subtask ${reward.use_progression ? 'progression-button-filled' : 'progression-button-outlined'}`}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault()
|
||||
const newSubtasks = [...subtasks]
|
||||
newSubtasks[index].rewards[rIndex].use_progression = !newSubtasks[index].rewards[rIndex].use_progression
|
||||
setSubtasks(newSubtasks)
|
||||
}}
|
||||
title={reward.use_progression ? 'Отключить прогрессию' : 'Включить прогрессию'}
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
|
||||
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Показываем ошибку валидации только если это ошибка валидации, не ошибка действия */}
|
||||
{error && (error.includes('обязательно') || error.includes('должны быть заполнены') || error.includes('нельзя одновременно')) && (
|
||||
<div className="error-message">{error}</div>
|
||||
|
||||
Reference in New Issue
Block a user