Исправление отображения проектов в условиях
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m21s

This commit is contained in:
poignatov
2026-01-19 13:07:17 +03:00
parent e3c81a36de
commit 6d468d6967
6 changed files with 197 additions and 44 deletions

View File

@@ -1 +1 @@
3.14.5 3.14.6

View File

@@ -334,6 +334,7 @@ type WishlistRequest struct {
} }
type UnlockConditionRequest struct { type UnlockConditionRequest struct {
ID *int `json:"id,omitempty"` // ID существующего условия (для сохранения чужих условий)
Type string `json:"type"` Type string `json:"type"`
TaskID *int `json:"task_id,omitempty"` TaskID *int `json:"task_id,omitempty"`
ProjectID *int `json:"project_id,omitempty"` ProjectID *int `json:"project_id,omitempty"`
@@ -2865,6 +2866,12 @@ func (a *App) initAuthDB() error {
// Не возвращаем ошибку, чтобы приложение могло запуститься // Не возвращаем ошибку, чтобы приложение могло запуститься
} }
// Apply migration 025: Remove wishlist conditions without user_id
if err := a.applyMigration025(); err != nil {
log.Printf("Warning: Failed to apply migration 025: %v", err)
// Не возвращаем ошибку, чтобы приложение могло запуститься
}
// Clean up expired refresh tokens (only those with expiration date set) // Clean up expired refresh tokens (only those with expiration date set)
a.DB.Exec("DELETE FROM refresh_tokens WHERE expires_at IS NOT NULL AND expires_at < NOW()") a.DB.Exec("DELETE FROM refresh_tokens WHERE expires_at IS NOT NULL AND expires_at < NOW()")
@@ -3268,6 +3275,50 @@ func (a *App) applyMigration024() error {
return nil return nil
} }
// applyMigration025 применяет миграцию 025_remove_conditions_without_user_id.sql
func (a *App) applyMigration025() error {
log.Printf("Applying migration 025: Remove wishlist conditions without user_id")
// Проверяем, есть ли условия без user_id
var count int
err := a.DB.QueryRow(`
SELECT COUNT(*)
FROM wishlist_conditions
WHERE user_id IS NULL
`).Scan(&count)
if err != nil {
return fmt.Errorf("failed to check conditions without user_id: %w", err)
}
if count == 0 {
log.Printf("Migration 025 already applied (no conditions without user_id), skipping")
return nil
}
log.Printf("Found %d conditions without user_id, removing them", count)
// Читаем SQL из файла миграции
migrationPath := "migrations/025_remove_conditions_without_user_id.sql"
if _, err := os.Stat(migrationPath); os.IsNotExist(err) {
// Пробуем альтернативный путь (в Docker)
migrationPath = "/migrations/025_remove_conditions_without_user_id.sql"
}
migrationSQL, err := os.ReadFile(migrationPath)
if err != nil {
return fmt.Errorf("failed to read migration file %s: %w", migrationPath, err)
}
// Выполняем миграцию
if _, err := a.DB.Exec(string(migrationSQL)); err != nil {
return fmt.Errorf("failed to execute migration 025: %w", err)
}
log.Printf("Migration 025 applied successfully, removed %d conditions without user_id", count)
return nil
}
func (a *App) initPlayLifeDB() error { func (a *App) initPlayLifeDB() error {
// Создаем таблицу projects // Создаем таблицу projects
createProjectsTable := ` createProjectsTable := `
@@ -9372,7 +9423,7 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool)
LEFT JOIN task_conditions tc ON wc.task_condition_id = tc.id LEFT JOIN task_conditions tc ON wc.task_condition_id = tc.id
LEFT JOIN tasks t ON tc.task_id = t.id LEFT JOIN tasks t ON tc.task_id = t.id
LEFT JOIN score_conditions sc ON wc.score_condition_id = sc.id LEFT JOIN score_conditions sc ON wc.score_condition_id = sc.id
LEFT JOIN projects p ON sc.project_id = p.id LEFT JOIN projects p ON sc.project_id = p.id AND p.deleted = FALSE
WHERE wi.user_id = $1 WHERE wi.user_id = $1
AND wi.deleted = FALSE AND wi.deleted = FALSE
AND ($2 = TRUE OR wi.completed = FALSE) AND ($2 = TRUE OR wi.completed = FALSE)
@@ -9457,6 +9508,10 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool)
if projectName.Valid { if projectName.Valid {
condition.ProjectName = &projectName.String condition.ProjectName = &projectName.String
} }
if projectID.Valid {
projectIDVal := int(projectID.Int64)
condition.ProjectID = &projectIDVal
}
if requiredPoints.Valid { if requiredPoints.Valid {
condition.RequiredPoints = &requiredPoints.Float64 condition.RequiredPoints = &requiredPoints.Float64
} }
@@ -9633,18 +9688,43 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool)
} }
// saveWishlistConditions сохраняет условия для желания // saveWishlistConditions сохраняет условия для желания
// userID - автор условий (пользователь, который создает/обновляет условия)
func (a *App) saveWishlistConditions( func (a *App) saveWishlistConditions(
tx *sql.Tx, tx *sql.Tx,
wishlistItemID int, wishlistItemID int,
userID int,
conditions []UnlockConditionRequest, conditions []UnlockConditionRequest,
) error { ) error {
// Удаляем старые условия // Получаем все существующие условия с их user_id перед удалением
_, err := tx.Exec(` existingConditions := make(map[int]int) // map[conditionID]userID
DELETE FROM wishlist_conditions rows, err := tx.Query(`
SELECT id, user_id
FROM wishlist_conditions
WHERE wishlist_item_id = $1 WHERE wishlist_item_id = $1
`, wishlistItemID) `, wishlistItemID)
if err != nil { if err != nil {
return err return fmt.Errorf("error getting existing conditions: %w", err)
}
defer rows.Close()
for rows.Next() {
var condID int
var condUserID sql.NullInt64
if err := rows.Scan(&condID, &condUserID); err != nil {
return fmt.Errorf("error scanning existing condition: %w", err)
}
if condUserID.Valid {
existingConditions[condID] = int(condUserID.Int64)
}
}
// Удаляем только условия текущего пользователя
_, err = tx.Exec(`
DELETE FROM wishlist_conditions
WHERE wishlist_item_id = $1 AND user_id = $2
`, wishlistItemID, userID)
if err != nil {
return fmt.Errorf("error deleting user conditions: %w", err)
} }
if len(conditions) == 0 { if len(conditions) == 0 {
@@ -9654,8 +9734,8 @@ func (a *App) saveWishlistConditions(
// Подготавливаем statement для вставки условий // Подготавливаем statement для вставки условий
stmt, err := tx.Prepare(` stmt, err := tx.Prepare(`
INSERT INTO wishlist_conditions INSERT INTO wishlist_conditions
(wishlist_item_id, task_condition_id, score_condition_id, display_order) (wishlist_item_id, user_id, task_condition_id, score_condition_id, display_order)
VALUES ($1, $2, $3, $4) VALUES ($1, $2, $3, $4, $5)
`) `)
if err != nil { if err != nil {
return err return err
@@ -9743,9 +9823,20 @@ func (a *App) saveWishlistConditions(
scoreConditionID = scID scoreConditionID = scID
} }
// Определяем user_id для условия:
// - Если условие имеет id и это условие существовало - используем его оригинальный user_id
// - Иначе - используем userID текущего пользователя
conditionUserID := userID
if condition.ID != nil {
if originalUserID, exists := existingConditions[*condition.ID]; exists {
conditionUserID = originalUserID
}
}
// Создаём связь // Создаём связь
_, err = stmt.Exec( _, err = stmt.Exec(
wishlistItemID, wishlistItemID,
conditionUserID,
taskConditionID, taskConditionID,
scoreConditionID, scoreConditionID,
displayOrder, displayOrder,
@@ -9937,7 +10028,7 @@ func (a *App) createWishlistHandler(w http.ResponseWriter, r *http.Request) {
// Сохраняем условия // Сохраняем условия
if len(req.UnlockConditions) > 0 { if len(req.UnlockConditions) > 0 {
err = a.saveWishlistConditions(tx, wishlistID, req.UnlockConditions) err = a.saveWishlistConditions(tx, wishlistID, userID, req.UnlockConditions)
if err != nil { if err != nil {
log.Printf("Error saving wishlist conditions: %v", err) log.Printf("Error saving wishlist conditions: %v", err)
sendErrorWithCORS(w, fmt.Sprintf("Error saving wishlist conditions: %v", err), http.StatusInternalServerError) sendErrorWithCORS(w, fmt.Sprintf("Error saving wishlist conditions: %v", err), http.StatusInternalServerError)
@@ -10088,7 +10179,7 @@ func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) {
LEFT JOIN task_conditions tc ON wc.task_condition_id = tc.id LEFT JOIN task_conditions tc ON wc.task_condition_id = tc.id
LEFT JOIN tasks t ON tc.task_id = t.id LEFT JOIN tasks t ON tc.task_id = t.id
LEFT JOIN score_conditions sc ON wc.score_condition_id = sc.id LEFT JOIN score_conditions sc ON wc.score_condition_id = sc.id
LEFT JOIN projects p ON sc.project_id = p.id LEFT JOIN projects p ON sc.project_id = p.id AND p.deleted = FALSE
WHERE wi.id = $1 WHERE wi.id = $1
AND wi.deleted = FALSE AND wi.deleted = FALSE
ORDER BY wc.display_order, wc.id ORDER BY wc.display_order, wc.id
@@ -10185,6 +10276,12 @@ func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) {
if projectName.Valid { if projectName.Valid {
condition.ProjectName = &projectName.String condition.ProjectName = &projectName.String
} }
if projectID.Valid {
projectIDVal := int(projectID.Int64)
condition.ProjectID = &projectIDVal
points, _ := a.calculateProjectPointsFromDate(int(projectID.Int64), startDate, conditionOwnerID)
condition.CurrentPoints = &points
}
if requiredPoints.Valid { if requiredPoints.Valid {
condition.RequiredPoints = &requiredPoints.Float64 condition.RequiredPoints = &requiredPoints.Float64
} }
@@ -10192,10 +10289,6 @@ func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) {
dateStr := startDate.Time.Format("2006-01-02") dateStr := startDate.Time.Format("2006-01-02")
condition.StartDate = &dateStr condition.StartDate = &dateStr
} }
if projectID.Valid {
points, _ := a.calculateProjectPointsFromDate(int(projectID.Int64), startDate, conditionOwnerID)
condition.CurrentPoints = &points
}
} }
item.UnlockConditions = append(item.UnlockConditions, condition) item.UnlockConditions = append(item.UnlockConditions, condition)
@@ -10358,7 +10451,7 @@ func (a *App) updateWishlistHandler(w http.ResponseWriter, r *http.Request) {
} }
// Сохраняем условия // Сохраняем условия
err = a.saveWishlistConditions(tx, itemID, req.UnlockConditions) err = a.saveWishlistConditions(tx, itemID, userID, req.UnlockConditions)
if err != nil { if err != nil {
log.Printf("Error saving wishlist conditions: %v", err) log.Printf("Error saving wishlist conditions: %v", err)
sendErrorWithCORS(w, fmt.Sprintf("Error saving wishlist conditions: %v", err), http.StatusInternalServerError) sendErrorWithCORS(w, fmt.Sprintf("Error saving wishlist conditions: %v", err), http.StatusInternalServerError)
@@ -10397,7 +10490,7 @@ func (a *App) updateWishlistHandler(w http.ResponseWriter, r *http.Request) {
LEFT JOIN task_conditions tc ON wc.task_condition_id = tc.id LEFT JOIN task_conditions tc ON wc.task_condition_id = tc.id
LEFT JOIN tasks t ON tc.task_id = t.id LEFT JOIN tasks t ON tc.task_id = t.id
LEFT JOIN score_conditions sc ON wc.score_condition_id = sc.id LEFT JOIN score_conditions sc ON wc.score_condition_id = sc.id
LEFT JOIN projects p ON sc.project_id = p.id LEFT JOIN projects p ON sc.project_id = p.id AND p.deleted = FALSE
WHERE wi.id = $1 WHERE wi.id = $1
AND wi.deleted = FALSE AND wi.deleted = FALSE
ORDER BY wc.display_order, wc.id ORDER BY wc.display_order, wc.id
@@ -10500,6 +10593,12 @@ func (a *App) updateWishlistHandler(w http.ResponseWriter, r *http.Request) {
if projectName.Valid { if projectName.Valid {
condition.ProjectName = &projectName.String condition.ProjectName = &projectName.String
} }
if projectID.Valid {
projectIDVal := int(projectID.Int64)
condition.ProjectID = &projectIDVal
points, _ := a.calculateProjectPointsFromDate(int(projectID.Int64), startDate, conditionOwnerID)
condition.CurrentPoints = &points
}
if requiredPoints.Valid { if requiredPoints.Valid {
condition.RequiredPoints = &requiredPoints.Float64 condition.RequiredPoints = &requiredPoints.Float64
} }
@@ -10507,10 +10606,6 @@ func (a *App) updateWishlistHandler(w http.ResponseWriter, r *http.Request) {
dateStr := startDate.Time.Format("2006-01-02") dateStr := startDate.Time.Format("2006-01-02")
condition.StartDate = &dateStr condition.StartDate = &dateStr
} }
if projectID.Valid {
points, _ := a.calculateProjectPointsFromDate(int(projectID.Int64), startDate, conditionOwnerID)
condition.CurrentPoints = &points
}
} }
item.UnlockConditions = append(item.UnlockConditions, condition) item.UnlockConditions = append(item.UnlockConditions, condition)
@@ -10944,11 +11039,13 @@ func (a *App) copyWishlistHandler(w http.ResponseWriter, r *http.Request) {
var link sql.NullString var link sql.NullString
var imagePath sql.NullString var imagePath sql.NullString
var ownerID int var ownerID int
var boardID sql.NullInt64
var authorID sql.NullInt64
err = a.DB.QueryRow(` err = a.DB.QueryRow(`
SELECT user_id, name, price, link, image_path SELECT user_id, name, price, link, image_path, board_id, author_id
FROM wishlist_items FROM wishlist_items
WHERE id = $1 AND deleted = FALSE WHERE id = $1 AND deleted = FALSE
`, itemID).Scan(&ownerID, &name, &price, &link, &imagePath) `, itemID).Scan(&ownerID, &name, &price, &link, &imagePath, &boardID, &authorID)
if err == sql.ErrNoRows || ownerID != userID { if err == sql.ErrNoRows || ownerID != userID {
sendErrorWithCORS(w, "Wishlist item not found", http.StatusNotFound) sendErrorWithCORS(w, "Wishlist item not found", http.StatusNotFound)
@@ -11039,11 +11136,23 @@ func (a *App) copyWishlistHandler(w http.ResponseWriter, r *http.Request) {
linkVal = link.String linkVal = link.String
} }
// Определяем значения для board_id и author_id
var boardIDVal, authorIDVal interface{}
if boardID.Valid {
boardIDVal = int(boardID.Int64)
}
if authorID.Valid {
authorIDVal = int(authorID.Int64)
} else {
// Если author_id не был установлен, используем текущего пользователя
authorIDVal = userID
}
err = tx.QueryRow(` err = tx.QueryRow(`
INSERT INTO wishlist_items (user_id, name, price, link, completed, deleted) INSERT INTO wishlist_items (user_id, board_id, author_id, name, price, link, completed, deleted)
VALUES ($1, $2, $3, $4, FALSE, FALSE) VALUES ($1, $2, $3, $4, $5, $6, FALSE, FALSE)
RETURNING id RETURNING id
`, userID, name+" (копия)", priceVal, linkVal).Scan(&newWishlistID) `, ownerID, boardIDVal, authorIDVal, name+" (копия)", priceVal, linkVal).Scan(&newWishlistID)
if err != nil { if err != nil {
log.Printf("Error creating wishlist copy: %v", err) log.Printf("Error creating wishlist copy: %v", err)
sendErrorWithCORS(w, fmt.Sprintf("Error creating wishlist copy: %v", err), http.StatusInternalServerError) sendErrorWithCORS(w, fmt.Sprintf("Error creating wishlist copy: %v", err), http.StatusInternalServerError)
@@ -11052,7 +11161,7 @@ func (a *App) copyWishlistHandler(w http.ResponseWriter, r *http.Request) {
// Сохраняем условия // Сохраняем условия
if len(conditions) > 0 { if len(conditions) > 0 {
err = a.saveWishlistConditions(tx, newWishlistID, conditions) err = a.saveWishlistConditions(tx, newWishlistID, userID, conditions)
if err != nil { if err != nil {
log.Printf("Error saving wishlist conditions: %v", err) log.Printf("Error saving wishlist conditions: %v", err)
sendErrorWithCORS(w, fmt.Sprintf("Error saving wishlist conditions: %v", err), http.StatusInternalServerError) sendErrorWithCORS(w, fmt.Sprintf("Error saving wishlist conditions: %v", err), http.StatusInternalServerError)
@@ -12022,7 +12131,7 @@ func (a *App) getBoardCompletedHandler(w http.ResponseWriter, r *http.Request) {
LEFT JOIN task_conditions tc ON wc.task_condition_id = tc.id LEFT JOIN task_conditions tc ON wc.task_condition_id = tc.id
LEFT JOIN tasks t ON tc.task_id = t.id LEFT JOIN tasks t ON tc.task_id = t.id
LEFT JOIN score_conditions sc ON wc.score_condition_id = sc.id LEFT JOIN score_conditions sc ON wc.score_condition_id = sc.id
LEFT JOIN projects p ON sc.project_id = p.id LEFT JOIN projects p ON sc.project_id = p.id AND p.deleted = FALSE
LEFT JOIN users u ON wc.user_id = u.id LEFT JOIN users u ON wc.user_id = u.id
WHERE wi.board_id = $1 WHERE wi.board_id = $1
AND wi.deleted = FALSE AND wi.deleted = FALSE
@@ -12178,6 +12287,7 @@ func (a *App) getWishlistItemsByBoard(boardID int, userID int) ([]WishlistItem,
wi.image_path, wi.image_path,
wi.link, wi.link,
wi.completed, wi.completed,
COALESCE(wi.author_id, wi.user_id) AS item_owner_id,
wc.id AS condition_id, wc.id AS condition_id,
wc.display_order, wc.display_order,
wc.task_condition_id, wc.task_condition_id,
@@ -12194,7 +12304,7 @@ func (a *App) getWishlistItemsByBoard(boardID int, userID int) ([]WishlistItem,
LEFT JOIN task_conditions tc ON wc.task_condition_id = tc.id LEFT JOIN task_conditions tc ON wc.task_condition_id = tc.id
LEFT JOIN tasks t ON tc.task_id = t.id LEFT JOIN tasks t ON tc.task_id = t.id
LEFT JOIN score_conditions sc ON wc.score_condition_id = sc.id LEFT JOIN score_conditions sc ON wc.score_condition_id = sc.id
LEFT JOIN projects p ON sc.project_id = p.id LEFT JOIN projects p ON sc.project_id = p.id AND p.deleted = FALSE
WHERE wi.board_id = $1 WHERE wi.board_id = $1
AND wi.deleted = FALSE AND wi.deleted = FALSE
AND wi.completed = FALSE AND wi.completed = FALSE
@@ -12216,6 +12326,7 @@ func (a *App) getWishlistItemsByBoard(boardID int, userID int) ([]WishlistItem,
var imagePath sql.NullString var imagePath sql.NullString
var link sql.NullString var link sql.NullString
var completed bool var completed bool
var itemOwnerID sql.NullInt64
var conditionID sql.NullInt64 var conditionID sql.NullInt64
var displayOrder sql.NullInt64 var displayOrder sql.NullInt64
var taskConditionID sql.NullInt64 var taskConditionID sql.NullInt64
@@ -12229,7 +12340,7 @@ func (a *App) getWishlistItemsByBoard(boardID int, userID int) ([]WishlistItem,
var startDate sql.NullTime var startDate sql.NullTime
err := rows.Scan( err := rows.Scan(
&itemID, &name, &price, &imagePath, &link, &completed, &itemID, &name, &price, &imagePath, &link, &completed, &itemOwnerID,
&conditionID, &displayOrder, &taskConditionID, &scoreConditionID, &conditionUserID, &conditionID, &displayOrder, &taskConditionID, &scoreConditionID, &conditionUserID,
&taskID, &taskName, &projectID, &projectName, &requiredPoints, &startDate, &taskID, &taskName, &projectID, &projectName, &requiredPoints, &startDate,
) )
@@ -12268,8 +12379,12 @@ func (a *App) getWishlistItemsByBoard(boardID int, userID int) ([]WishlistItem,
DisplayOrder: int(displayOrder.Int64), DisplayOrder: int(displayOrder.Int64),
} }
// Используем user_id из условия, если он есть, иначе используем текущего пользователя // Используем user_id из условия, если он есть, иначе используем владельца желания
conditionOwnerID := userID if !itemOwnerID.Valid {
log.Printf("Warning: item_owner_id is NULL for wishlist item %d, skipping condition", itemID)
continue
}
conditionOwnerID := int(itemOwnerID.Int64)
if conditionUserID.Valid { if conditionUserID.Valid {
conditionOwnerID = int(conditionUserID.Int64) conditionOwnerID = int(conditionUserID.Int64)
} }
@@ -12291,6 +12406,13 @@ func (a *App) getWishlistItemsByBoard(boardID int, userID int) ([]WishlistItem,
if projectName.Valid { if projectName.Valid {
condition.ProjectName = &projectName.String condition.ProjectName = &projectName.String
} }
if projectID.Valid {
projectIDVal := int(projectID.Int64)
condition.ProjectID = &projectIDVal
// Считаем текущие баллы для владельца условия
points, _ := a.calculateProjectPointsFromDate(int(projectID.Int64), startDate, conditionOwnerID)
condition.CurrentPoints = &points
}
if requiredPoints.Valid { if requiredPoints.Valid {
condition.RequiredPoints = &requiredPoints.Float64 condition.RequiredPoints = &requiredPoints.Float64
} }
@@ -12298,11 +12420,6 @@ func (a *App) getWishlistItemsByBoard(boardID int, userID int) ([]WishlistItem,
dateStr := startDate.Time.Format("2006-01-02") dateStr := startDate.Time.Format("2006-01-02")
condition.StartDate = &dateStr condition.StartDate = &dateStr
} }
// Считаем текущие баллы для владельца условия
if projectID.Valid {
points, _ := a.calculateProjectPointsFromDate(int(projectID.Int64), startDate, conditionOwnerID)
condition.CurrentPoints = &points
}
} }
item.UnlockConditions = append(item.UnlockConditions, condition) item.UnlockConditions = append(item.UnlockConditions, condition)

View File

@@ -0,0 +1,13 @@
-- Migration: Remove wishlist conditions without user_id
-- These conditions should not exist as every condition must have an owner
-- This migration removes orphaned conditions that were created before the fix
-- ============================================
-- Remove conditions without user_id
-- ============================================
DELETE FROM wishlist_conditions WHERE user_id IS NULL;
-- ============================================
-- Comments
-- ============================================
COMMENT ON COLUMN wishlist_conditions.user_id IS 'Owner of this condition. Each user has their own goals on shared boards. Required field.';

View File

@@ -1,6 +1,6 @@
{ {
"name": "play-life-web", "name": "play-life-web",
"version": "3.14.5", "version": "3.14.6",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -450,15 +450,31 @@ function Wishlist({ onNavigate, refreshTrigger = 0, isActive = false, initialBoa
}) })
if (!response.ok) { if (!response.ok) {
throw new Error('Ошибка при копировании') const errorText = await response.text().catch(() => '')
throw new Error(errorText || 'Ошибка при копировании')
} }
const newItem = await response.json() const newItem = await response.json()
setSelectedItem(null) setSelectedItem(null)
// Очищаем кэш для текущей доски, чтобы новое желание появилось в списке
if (selectedBoardId) {
try {
localStorage.removeItem(`${ITEMS_CACHE_KEY}_${selectedBoardId}`)
} catch (err) {
console.error('Error clearing cache:', err)
}
}
// Обновляем список
await fetchItems()
// Открываем форму редактирования для нового желания
onNavigate?.('wishlist-form', { wishlistId: newItem.id, boardId: selectedBoardId }) onNavigate?.('wishlist-form', { wishlistId: newItem.id, boardId: selectedBoardId })
} catch (err) { } catch (err) {
setError(err.message) console.error('Error copying wishlist item:', err)
setError(err.message || 'Ошибка при копировании')
setSelectedItem(null) setSelectedItem(null)
} }
} }

View File

@@ -88,9 +88,12 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
setImageUrl(data.image_url || null) setImageUrl(data.image_url || null)
if (data.unlock_conditions) { if (data.unlock_conditions) {
setUnlockConditions(data.unlock_conditions.map((cond, idx) => ({ setUnlockConditions(data.unlock_conditions.map((cond, idx) => ({
id: cond.id || null,
type: cond.type, type: cond.type,
task_id: cond.type === 'task_completion' ? tasks.find(t => t.name === cond.task_name)?.id : null, task_id: cond.type === 'task_completion' ? (cond.task_id || tasks.find(t => t.name === cond.task_name)?.id) : null,
project_id: cond.type === 'project_points' ? projects.find(p => p.project_name === cond.project_name)?.project_id : null, task_name: cond.task_name || null,
project_id: cond.type === 'project_points' ? (cond.project_id || projects.find(p => p.project_name === cond.project_name)?.project_id) : null,
project_name: cond.project_name || null,
required_points: cond.required_points || null, required_points: cond.required_points || null,
start_date: cond.start_date || null, start_date: cond.start_date || null,
display_order: idx, display_order: idx,
@@ -239,9 +242,12 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
setImageFile(null) // Сбрасываем imageFile при загрузке существующего желания setImageFile(null) // Сбрасываем imageFile при загрузке существующего желания
if (data.unlock_conditions) { if (data.unlock_conditions) {
setUnlockConditions(data.unlock_conditions.map((cond, idx) => ({ setUnlockConditions(data.unlock_conditions.map((cond, idx) => ({
id: cond.id || null,
type: cond.type, type: cond.type,
task_id: cond.type === 'task_completion' ? tasks.find(t => t.name === cond.task_name)?.id : null, task_id: cond.type === 'task_completion' ? (cond.task_id || tasks.find(t => t.name === cond.task_name)?.id) : null,
project_id: cond.type === 'project_points' ? projects.find(p => p.project_name === cond.project_name)?.project_id : null, task_name: cond.task_name || null,
project_id: cond.type === 'project_points' ? (cond.project_id || projects.find(p => p.project_name === cond.project_name)?.project_id) : null,
project_name: cond.project_name || null,
required_points: cond.required_points || null, required_points: cond.required_points || null,
start_date: cond.start_date || null, start_date: cond.start_date || null,
display_order: idx, display_order: idx,
@@ -531,6 +537,7 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
price: price ? parseFloat(price) : null, price: price ? parseFloat(price) : null,
link: link.trim() || null, link: link.trim() || null,
unlock_conditions: unlockConditions.map(cond => ({ unlock_conditions: unlockConditions.map(cond => ({
id: cond.id || null,
type: cond.type, type: cond.type,
task_id: cond.type === 'task_completion' ? cond.task_id : null, task_id: cond.type === 'task_completion' ? cond.task_id : null,
project_id: cond.type === 'project_points' ? cond.project_id : null, project_id: cond.type === 'project_points' ? cond.project_id : null,
@@ -784,7 +791,7 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
> >
{cond.type === 'task_completion' {cond.type === 'task_completion'
? `Задача: ${tasks.find(t => t.id === cond.task_id)?.name || 'Не выбрана'}` ? `Задача: ${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')}` : ' за всё время'}`} : `Баллы: ${cond.required_points} в ${projects.find(p => p.project_id === cond.project_id)?.project_name || cond.project_name || 'Не выбран'}${cond.start_date ? ` с ${new Date(cond.start_date + 'T00:00:00').toLocaleDateString('ru-RU')}` : ' за всё время'}`}
</span> </span>
<button <button
type="button" type="button"