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:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "play-life-web",
|
"name": "play-life-web",
|
||||||
"version": "6.9.2",
|
"version": "6.10.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -227,23 +227,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.add-subtask-button {
|
.add-subtask-button {
|
||||||
padding: 0.375rem;
|
padding: 0.5rem;
|
||||||
background: #6366f1;
|
background: white;
|
||||||
color: white;
|
color: #3498db;
|
||||||
border: none;
|
border: 1px dashed #bae6fd;
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-shrink: 0;
|
width: 100%;
|
||||||
min-width: 2rem;
|
height: 2.5rem;
|
||||||
height: 2rem;
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-subtask-button:hover {
|
.add-subtask-button:hover {
|
||||||
background: #4f46e5;
|
background: #e0f2fe;
|
||||||
|
border-color: #3498db;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subtask-form-item {
|
.subtask-form-item {
|
||||||
@@ -311,9 +312,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.remove-subtask-button {
|
.remove-subtask-button {
|
||||||
padding: 0.5rem;
|
padding: 0.25rem;
|
||||||
background: #ef4444;
|
background: none;
|
||||||
color: white;
|
color: #9ca3af;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -322,12 +323,12 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
min-width: 2.5rem;
|
min-width: 2rem;
|
||||||
height: 2.5rem;
|
height: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.remove-subtask-button:hover {
|
.remove-subtask-button:hover {
|
||||||
background: #dc2626;
|
color: #ef4444;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-message {
|
.error-message {
|
||||||
@@ -459,13 +460,23 @@
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.test-config-section > label {
|
.test-config-section > label,
|
||||||
|
.test-config-section > .subtasks-header > label {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #3498db;
|
color: #3498db;
|
||||||
margin-bottom: 1rem !important;
|
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 {
|
.test-config-fields {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
|
|||||||
@@ -920,6 +920,148 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
|
|||||||
/>
|
/>
|
||||||
</div>
|
</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 && (
|
{wishlistInfo && (
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
@@ -948,29 +1090,6 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
|
|||||||
</div>
|
</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 */}
|
{/* Test-specific fields */}
|
||||||
{isTest && (
|
{isTest && (
|
||||||
<div className="form-group test-config-section">
|
<div className="form-group test-config-section">
|
||||||
@@ -1242,136 +1361,6 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
|
|||||||
)}
|
)}
|
||||||
</div>
|
</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('нельзя одновременно')) && (
|
{error && (error.includes('обязательно') || error.includes('должны быть заполнены') || error.includes('нельзя одновременно')) && (
|
||||||
<div className="error-message">{error}</div>
|
<div className="error-message">{error}</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user