v3.9.5: Добавлена возможность копирования желаний, исправлена замена изображений
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 48s

This commit is contained in:
poignatov
2026-01-12 17:42:51 +03:00
parent 3cf3cd4edb
commit 705eb2400e
9 changed files with 509 additions and 162 deletions

View File

@@ -8,7 +8,7 @@ const API_URL = '/api/wishlist'
const TASKS_API_URL = '/api/tasks'
const PROJECTS_API_URL = '/projects'
function WishlistForm({ onNavigate, wishlistId }) {
function WishlistForm({ onNavigate, wishlistId, editConditionIndex }) {
const { authFetch } = useAuth()
const [name, setName] = useState('')
const [price, setPrice] = useState('')
@@ -21,6 +21,7 @@ function WishlistForm({ onNavigate, wishlistId }) {
const [croppedAreaPixels, setCroppedAreaPixels] = useState(null)
const [unlockConditions, setUnlockConditions] = useState([])
const [showConditionForm, setShowConditionForm] = useState(false)
const [editingConditionIndex, setEditingConditionIndex] = useState(null)
const [tasks, setTasks] = useState([])
const [projects, setProjects] = useState([])
const [loading, setLoading] = useState(false)
@@ -28,6 +29,7 @@ function WishlistForm({ onNavigate, wishlistId }) {
const [toastMessage, setToastMessage] = useState(null)
const [loadingWishlist, setLoadingWishlist] = useState(false)
const [fetchingMetadata, setFetchingMetadata] = useState(false)
const fileInputRef = useRef(null)
// Загрузка задач и проектов
useEffect(() => {
@@ -62,6 +64,14 @@ function WishlistForm({ onNavigate, wishlistId }) {
}
}, [wishlistId, tasks, projects])
// Открываем форму редактирования условия, если передан editConditionIndex
useEffect(() => {
if (editConditionIndex !== undefined && editConditionIndex !== null && unlockConditions.length > editConditionIndex) {
setEditingConditionIndex(editConditionIndex)
setShowConditionForm(true)
}
}, [editConditionIndex, unlockConditions])
const loadWishlist = async () => {
setLoadingWishlist(true)
try {
@@ -74,6 +84,7 @@ function WishlistForm({ onNavigate, wishlistId }) {
setPrice(data.price ? String(data.price) : '')
setLink(data.link || '')
setImageUrl(data.image_url || null)
setImageFile(null) // Сбрасываем imageFile при загрузке существующего желания
if (data.unlock_conditions) {
setUnlockConditions(data.unlock_conditions.map((cond, idx) => ({
type: cond.type,
@@ -256,12 +267,32 @@ function WishlistForm({ onNavigate, wishlistId }) {
}
const handleAddCondition = () => {
setEditingConditionIndex(null)
setShowConditionForm(true)
}
const handleEditCondition = (index) => {
setEditingConditionIndex(index)
setShowConditionForm(true)
}
const handleConditionSubmit = (condition) => {
setUnlockConditions([...unlockConditions, { ...condition, display_order: unlockConditions.length }])
if (editingConditionIndex !== null) {
// Редактирование существующего условия
setUnlockConditions(prev => prev.map((cond, idx) =>
idx === editingConditionIndex ? { ...condition, display_order: idx } : cond
))
} else {
// Добавление нового условия
setUnlockConditions([...unlockConditions, { ...condition, display_order: unlockConditions.length }])
}
setShowConditionForm(false)
setEditingConditionIndex(null)
}
const handleConditionCancel = () => {
setShowConditionForm(false)
setEditingConditionIndex(null)
}
const handleRemoveCondition = (index) => {
@@ -331,6 +362,12 @@ function WishlistForm({ onNavigate, wishlistId }) {
if (!imageResponse.ok) {
setToastMessage({ text: 'Желание сохранено, но ошибка при загрузке картинки', type: 'warning' })
} else {
// Обновляем imageUrl после успешной загрузки
const imageData = await imageResponse.json()
if (imageData.image_url) {
setImageUrl(imageData.image_url)
}
}
}
@@ -429,7 +466,13 @@ function WishlistForm({ onNavigate, wishlistId }) {
<label>Картинка</label>
{imageUrl && !showCropper && (
<div className="image-preview">
<img src={imageUrl} alt="Preview" />
<img
src={imageUrl}
alt="Preview"
onClick={() => fileInputRef.current?.click()}
style={{ cursor: 'pointer' }}
title="Нажмите, чтобы изменить"
/>
<button
type="button"
onClick={() => {
@@ -442,14 +485,14 @@ function WishlistForm({ onNavigate, wishlistId }) {
</button>
</div>
)}
{!imageUrl && (
<input
type="file"
accept="image/*"
onChange={handleImageSelect}
className="form-input"
/>
)}
<input
ref={fileInputRef}
type="file"
accept="image/*"
onChange={handleImageSelect}
className="form-input"
style={{ display: imageUrl ? 'none' : 'block' }}
/>
</div>
{showCropper && (
@@ -495,7 +538,10 @@ function WishlistForm({ onNavigate, wishlistId }) {
<div className="conditions-list">
{unlockConditions.map((cond, idx) => (
<div key={idx} className="condition-item">
<span>
<span
className="condition-item-text"
onClick={() => handleEditCondition(idx)}
>
{cond.type === 'task_completion'
? `Задача: ${tasks.find(t => t.id === cond.task_id)?.name || 'Не выбрана'}`
: `Баллы: ${cond.required_points} в ${projects.find(p => p.project_id === cond.project_id)?.project_name || 'Не выбран'}${cond.start_date ? ` с ${new Date(cond.start_date + 'T00:00:00').toLocaleDateString('ru-RU')}` : ' за всё время'}`}
@@ -516,7 +562,7 @@ function WishlistForm({ onNavigate, wishlistId }) {
onClick={handleAddCondition}
className="add-condition-button"
>
Добавить условие
Добавить цель
</button>
</div>
@@ -534,7 +580,8 @@ function WishlistForm({ onNavigate, wishlistId }) {
tasks={tasks}
projects={projects}
onSubmit={handleConditionSubmit}
onCancel={() => setShowConditionForm(false)}
onCancel={handleConditionCancel}
editingCondition={editingConditionIndex !== null ? unlockConditions[editingConditionIndex] : null}
/>
)}
@@ -627,13 +674,15 @@ function DateSelector({ value, onChange, placeholder = "За всё время"
)
}
// Компонент формы условия разблокировки
function ConditionForm({ tasks, projects, onSubmit, onCancel }) {
const [type, setType] = useState('project_points')
const [taskId, setTaskId] = useState('')
const [projectId, setProjectId] = useState('')
const [requiredPoints, setRequiredPoints] = useState('')
const [startDate, setStartDate] = useState('')
// Компонент формы цели
function ConditionForm({ tasks, projects, onSubmit, onCancel, editingCondition }) {
const [type, setType] = useState(editingCondition?.type || 'project_points')
const [taskId, setTaskId] = useState(editingCondition?.task_id?.toString() || '')
const [projectId, setProjectId] = useState(editingCondition?.project_id?.toString() || '')
const [requiredPoints, setRequiredPoints] = useState(editingCondition?.required_points?.toString() || '')
const [startDate, setStartDate] = useState(editingCondition?.start_date || '')
const isEditing = editingCondition !== null
const handleSubmit = (e) => {
e.preventDefault()
@@ -666,7 +715,7 @@ function ConditionForm({ tasks, projects, onSubmit, onCancel }) {
return (
<div className="condition-form-overlay" onClick={onCancel}>
<div className="condition-form" onClick={(e) => e.stopPropagation()}>
<h3>Добавить условие разблокировки</h3>
<h3>{isEditing ? 'Редактировать цель' : 'Добавить цель'}</h3>
<form onSubmit={handleSubmit}>
<div className="form-group">
<label>Тип условия</label>
@@ -742,7 +791,7 @@ function ConditionForm({ tasks, projects, onSubmit, onCancel }) {
Отмена
</button>
<button type="submit" className="submit-button">
Добавить
{isEditing ? 'Сохранить' : 'Добавить'}
</button>
</div>
</form>