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

This commit is contained in:
poignatov
2026-01-14 18:51:03 +03:00
parent c654a01116
commit e3c81a36de
6 changed files with 348 additions and 108 deletions

View File

@@ -1 +1 @@
3.14.4 3.14.5

View File

@@ -9976,6 +9976,45 @@ func (a *App) createWishlistHandler(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(createdItem) json.NewEncoder(w).Encode(createdItem)
} }
// checkWishlistAccess проверяет доступ пользователя к желанию
// Возвращает (hasAccess, itemUserID, boardID, error)
func (a *App) checkWishlistAccess(itemID int, userID int) (bool, int, sql.NullInt64, error) {
var itemUserID int
var boardID sql.NullInt64
err := a.DB.QueryRow(`
SELECT user_id, board_id
FROM wishlist_items
WHERE id = $1 AND deleted = FALSE
`, itemID).Scan(&itemUserID, &boardID)
if err == sql.ErrNoRows {
return false, 0, sql.NullInt64{}, err
}
if err != nil {
return false, 0, sql.NullInt64{}, err
}
// Проверяем доступ: владелец ИЛИ участник доски
hasAccess := itemUserID == userID
if !hasAccess && boardID.Valid {
var ownerID int
err = a.DB.QueryRow(`SELECT owner_id FROM wishlist_boards WHERE id = $1 AND deleted = FALSE`, boardID.Int64).Scan(&ownerID)
if err == nil {
hasAccess = ownerID == userID
if !hasAccess {
var isMember bool
err = a.DB.QueryRow(`SELECT EXISTS(SELECT 1 FROM wishlist_board_members WHERE board_id = $1 AND user_id = $2)`,
int(boardID.Int64), userID).Scan(&isMember)
if err == nil {
hasAccess = isMember
}
}
}
}
return hasAccess, itemUserID, boardID, nil
}
// getWishlistItemHandler возвращает одно желание // getWishlistItemHandler возвращает одно желание
func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) { func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == "OPTIONS" { if r.Method == "OPTIONS" {
@@ -9999,45 +10038,28 @@ func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) {
} }
// Проверяем доступ к желанию // Проверяем доступ к желанию
var itemUserID int hasAccess, itemUserID, boardID, err := a.checkWishlistAccess(itemID, userID)
var boardID sql.NullInt64
err = a.DB.QueryRow(`
SELECT user_id, board_id
FROM wishlist_items
WHERE id = $1 AND deleted = FALSE
`, itemID).Scan(&itemUserID, &boardID)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
log.Printf("Wishlist item not found: id=%d, userID=%d", itemID, userID)
sendErrorWithCORS(w, "Wishlist item not found", http.StatusNotFound) sendErrorWithCORS(w, "Wishlist item not found", http.StatusNotFound)
return return
} }
if err != nil { if err != nil {
log.Printf("Error getting wishlist item: %v", err) log.Printf("Error getting wishlist item (id=%d, userID=%d): %v", itemID, userID, err)
sendErrorWithCORS(w, "Error getting wishlist item", http.StatusInternalServerError) sendErrorWithCORS(w, "Error getting wishlist item", http.StatusInternalServerError)
return return
} }
// Проверяем доступ: владелец ИЛИ участник доски log.Printf("Wishlist item found: id=%d, itemUserID=%d, boardID=%v, currentUserID=%d", itemID, itemUserID, boardID, userID)
hasAccess := itemUserID == userID
if !hasAccess && boardID.Valid {
var ownerID int
err = a.DB.QueryRow(`SELECT owner_id FROM wishlist_boards WHERE id = $1 AND deleted = FALSE`, boardID.Int64).Scan(&ownerID)
if err == nil {
hasAccess = ownerID == userID
if !hasAccess {
var isMember bool
a.DB.QueryRow(`SELECT EXISTS(SELECT 1 FROM wishlist_board_members WHERE board_id = $1 AND user_id = $2)`,
int(boardID.Int64), userID).Scan(&isMember)
hasAccess = isMember
}
}
}
if !hasAccess { if !hasAccess {
log.Printf("Access denied for wishlist item: id=%d, itemUserID=%d, boardID=%v, currentUserID=%d", itemID, itemUserID, boardID, userID)
sendErrorWithCORS(w, "Access denied", http.StatusForbidden) sendErrorWithCORS(w, "Access denied", http.StatusForbidden)
return return
} }
log.Printf("Access granted for wishlist item: id=%d, itemUserID=%d, boardID=%v, currentUserID=%d", itemID, itemUserID, boardID, userID)
// Сохраняем itemUserID для использования в качестве fallback, если conditionUserID NULL // Сохраняем itemUserID для использования в качестве fallback, если conditionUserID NULL
itemOwnerID := itemUserID itemOwnerID := itemUserID
@@ -10262,8 +10284,11 @@ func (a *App) updateWishlistHandler(w http.ResponseWriter, r *http.Request) {
} }
setCORSHeaders(w) setCORSHeaders(w)
log.Printf("updateWishlistHandler called: method=%s, path=%s", r.Method, r.URL.Path)
userID, ok := getUserIDFromContext(r) userID, ok := getUserIDFromContext(r)
if !ok { if !ok {
log.Printf("updateWishlistHandler: Unauthorized")
sendErrorWithCORS(w, "Unauthorized", http.StatusUnauthorized) sendErrorWithCORS(w, "Unauthorized", http.StatusUnauthorized)
return return
} }
@@ -10271,26 +10296,34 @@ func (a *App) updateWishlistHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
itemID, err := strconv.Atoi(vars["id"]) itemID, err := strconv.Atoi(vars["id"])
if err != nil { if err != nil {
log.Printf("updateWishlistHandler: Invalid wishlist ID: %v", err)
sendErrorWithCORS(w, "Invalid wishlist ID", http.StatusBadRequest) sendErrorWithCORS(w, "Invalid wishlist ID", http.StatusBadRequest)
return return
} }
// Проверяем владельца log.Printf("updateWishlistHandler: itemID=%d, userID=%d", itemID, userID)
var ownerID int
err = a.DB.QueryRow(` // Проверяем доступ к желанию
SELECT user_id FROM wishlist_items hasAccess, _, _, err := a.checkWishlistAccess(itemID, userID)
WHERE id = $1 AND deleted = FALSE if err == sql.ErrNoRows {
`, itemID).Scan(&ownerID) log.Printf("updateWishlistHandler: Wishlist item not found: id=%d, userID=%d", itemID, userID)
if err == sql.ErrNoRows || ownerID != userID {
sendErrorWithCORS(w, "Wishlist item not found", http.StatusNotFound) sendErrorWithCORS(w, "Wishlist item not found", http.StatusNotFound)
return return
} }
if err != nil { if err != nil {
log.Printf("Error checking wishlist ownership: %v", err) log.Printf("updateWishlistHandler: Error getting wishlist item (id=%d, userID=%d): %v", itemID, userID, err)
sendErrorWithCORS(w, fmt.Sprintf("Error checking wishlist ownership: %v", err), http.StatusInternalServerError) sendErrorWithCORS(w, "Error getting wishlist item", http.StatusInternalServerError)
return return
} }
if !hasAccess {
log.Printf("updateWishlistHandler: Access denied: id=%d, userID=%d", itemID, userID)
sendErrorWithCORS(w, "Access denied", http.StatusForbidden)
return
}
log.Printf("updateWishlistHandler: Access granted: id=%d, userID=%d", itemID, userID)
var req WishlistRequest var req WishlistRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
log.Printf("Error decoding wishlist request: %v", err) log.Printf("Error decoding wishlist request: %v", err)
@@ -10311,11 +10344,12 @@ func (a *App) updateWishlistHandler(w http.ResponseWriter, r *http.Request) {
} }
defer tx.Rollback() defer tx.Rollback()
// Обновляем желание (не проверяем user_id в WHERE, так как доступ уже проверен выше)
_, err = tx.Exec(` _, err = tx.Exec(`
UPDATE wishlist_items UPDATE wishlist_items
SET name = $1, price = $2, link = $3, updated_at = NOW() SET name = $1, price = $2, link = $3, updated_at = NOW()
WHERE id = $4 AND user_id = $5 WHERE id = $4
`, strings.TrimSpace(req.Name), req.Price, req.Link, itemID, userID) `, strings.TrimSpace(req.Name), req.Price, req.Link, itemID)
if err != nil { if err != nil {
log.Printf("Error updating wishlist item: %v", err) log.Printf("Error updating wishlist item: %v", err)
@@ -10337,27 +10371,189 @@ func (a *App) updateWishlistHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
// Получаем обновлённое желание // Получаем обновлённое желание через getWishlistItemHandler логику
items, err := a.getWishlistItemsWithConditions(userID, true) // Используем тот же запрос, что и в getWishlistItemHandler
query := `
SELECT
wi.id,
wi.name,
wi.price,
wi.image_path,
wi.link,
wi.completed,
wc.id AS condition_id,
wc.display_order,
wc.task_condition_id,
wc.score_condition_id,
wc.user_id AS condition_user_id,
tc.task_id,
t.name AS task_name,
sc.project_id,
p.name AS project_name,
sc.required_points,
sc.start_date
FROM wishlist_items wi
LEFT JOIN wishlist_conditions wc ON wi.id = wc.wishlist_item_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 score_conditions sc ON wc.score_condition_id = sc.id
LEFT JOIN projects p ON sc.project_id = p.id
WHERE wi.id = $1
AND wi.deleted = FALSE
ORDER BY wc.display_order, wc.id
`
rows, err := a.DB.Query(query, itemID)
if err != nil { if err != nil {
log.Printf("Error getting updated wishlist item: %v", err) log.Printf("Error querying updated wishlist item: %v", err)
sendErrorWithCORS(w, fmt.Sprintf("Error getting updated wishlist item: %v", err), http.StatusInternalServerError) sendErrorWithCORS(w, "Error getting updated wishlist item", http.StatusInternalServerError)
return return
} }
defer rows.Close()
itemsMap := make(map[int]*WishlistItem)
var itemOwnerID int
for rows.Next() {
var itemID int
var name string
var price sql.NullFloat64
var imagePath sql.NullString
var link sql.NullString
var completed bool
var conditionID sql.NullInt64
var displayOrder sql.NullInt64
var taskConditionID sql.NullInt64
var scoreConditionID sql.NullInt64
var conditionUserID sql.NullInt64
var taskID sql.NullInt64
var taskName sql.NullString
var projectID sql.NullInt64
var projectName sql.NullString
var requiredPoints sql.NullFloat64
var startDate sql.NullTime
err := rows.Scan(
&itemID, &name, &price, &imagePath, &link, &completed,
&conditionID, &displayOrder, &taskConditionID, &scoreConditionID, &conditionUserID,
&taskID, &taskName, &projectID, &projectName, &requiredPoints, &startDate,
)
if err != nil {
log.Printf("Error scanning updated wishlist item: %v", err)
continue
}
item, exists := itemsMap[itemID]
if !exists {
// Получаем user_id для этого желания
err = a.DB.QueryRow(`SELECT user_id FROM wishlist_items WHERE id = $1`, itemID).Scan(&itemOwnerID)
if err != nil {
log.Printf("Error getting item owner: %v", err)
continue
}
item = &WishlistItem{
ID: itemID,
Name: name,
Completed: completed,
UnlockConditions: []UnlockConditionDisplay{},
}
if price.Valid {
item.Price = &price.Float64
}
if imagePath.Valid && imagePath.String != "" {
url := imagePath.String
if !strings.HasPrefix(url, "http") {
url = url + "?t=" + strconv.FormatInt(time.Now().Unix(), 10)
}
item.ImageURL = &url
}
if link.Valid {
item.Link = &link.String
}
itemsMap[itemID] = item
}
if conditionID.Valid {
condition := UnlockConditionDisplay{
ID: int(conditionID.Int64),
DisplayOrder: int(displayOrder.Int64),
}
conditionOwnerID := itemOwnerID
if conditionUserID.Valid {
conditionOwnerID = int(conditionUserID.Int64)
}
if taskConditionID.Valid {
condition.Type = "task_completion"
if taskName.Valid {
condition.TaskName = &taskName.String
}
if taskID.Valid {
var taskCompleted int
a.DB.QueryRow(`SELECT completed FROM tasks WHERE id = $1 AND user_id = $2`, taskID.Int64, conditionOwnerID).Scan(&taskCompleted)
isCompleted := taskCompleted > 0
condition.TaskCompleted = &isCompleted
}
} else if scoreConditionID.Valid {
condition.Type = "project_points"
if projectName.Valid {
condition.ProjectName = &projectName.String
}
if requiredPoints.Valid {
condition.RequiredPoints = &requiredPoints.Float64
}
if startDate.Valid {
dateStr := startDate.Time.Format("2006-01-02")
condition.StartDate = &dateStr
}
if projectID.Valid {
points, _ := a.calculateProjectPointsFromDate(int(projectID.Int64), startDate, conditionOwnerID)
condition.CurrentPoints = &points
}
}
item.UnlockConditions = append(item.UnlockConditions, condition)
}
}
var updatedItem *WishlistItem var updatedItem *WishlistItem
for i := range items { for _, it := range itemsMap {
if items[i].ID == itemID { if it.ID == itemID {
updatedItem = &items[i] updatedItem = it
break break
} }
} }
if updatedItem == nil { if updatedItem == nil {
log.Printf("Updated item not found: id=%d", itemID)
sendErrorWithCORS(w, "Updated item not found", http.StatusInternalServerError) sendErrorWithCORS(w, "Updated item not found", http.StatusInternalServerError)
return return
} }
// Проверяем разблокировку
updatedItem.Unlocked = true
if len(updatedItem.UnlockConditions) > 0 {
for _, cond := range updatedItem.UnlockConditions {
if cond.Type == "task_completion" {
if cond.TaskCompleted == nil || !*cond.TaskCompleted {
updatedItem.Unlocked = false
break
}
} else if cond.Type == "project_points" {
if cond.CurrentPoints == nil || cond.RequiredPoints == nil || *cond.CurrentPoints < *cond.RequiredPoints {
updatedItem.Unlocked = false
break
}
}
}
}
unlocked, err := a.checkWishlistUnlock(itemID, userID)
if err == nil {
updatedItem.Unlocked = unlocked
}
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(updatedItem) json.NewEncoder(w).Encode(updatedItem)
} }
@@ -10384,27 +10580,27 @@ func (a *App) deleteWishlistHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
// Проверяем владельца // Проверяем доступ к желанию
var ownerID int hasAccess, _, _, err := a.checkWishlistAccess(itemID, userID)
err = a.DB.QueryRow(` if err == sql.ErrNoRows {
SELECT user_id FROM wishlist_items
WHERE id = $1 AND deleted = FALSE
`, itemID).Scan(&ownerID)
if err == sql.ErrNoRows || ownerID != userID {
sendErrorWithCORS(w, "Wishlist item not found", http.StatusNotFound) sendErrorWithCORS(w, "Wishlist item not found", http.StatusNotFound)
return return
} }
if err != nil { if err != nil {
log.Printf("Error checking wishlist ownership: %v", err) log.Printf("Error checking wishlist access: %v", err)
sendErrorWithCORS(w, fmt.Sprintf("Error checking wishlist ownership: %v", err), http.StatusInternalServerError) sendErrorWithCORS(w, fmt.Sprintf("Error checking wishlist access: %v", err), http.StatusInternalServerError)
return
}
if !hasAccess {
sendErrorWithCORS(w, "Access denied", http.StatusForbidden)
return return
} }
_, err = a.DB.Exec(` _, err = a.DB.Exec(`
UPDATE wishlist_items UPDATE wishlist_items
SET deleted = TRUE, updated_at = NOW() SET deleted = TRUE, updated_at = NOW()
WHERE id = $1 AND user_id = $2 WHERE id = $1
`, itemID, userID) `, itemID)
if err != nil { if err != nil {
log.Printf("Error deleting wishlist item: %v", err) log.Printf("Error deleting wishlist item: %v", err)
@@ -10441,19 +10637,19 @@ func (a *App) uploadWishlistImageHandler(w http.ResponseWriter, r *http.Request)
return return
} }
// Проверяем владельца // Проверяем доступ к желанию
var ownerID int hasAccess, _, _, err := a.checkWishlistAccess(wishlistID, userID)
err = a.DB.QueryRow(` if err == sql.ErrNoRows {
SELECT user_id FROM wishlist_items
WHERE id = $1 AND deleted = FALSE
`, wishlistID).Scan(&ownerID)
if err == sql.ErrNoRows || ownerID != userID {
sendErrorWithCORS(w, "Wishlist item not found", http.StatusNotFound) sendErrorWithCORS(w, "Wishlist item not found", http.StatusNotFound)
return return
} }
if err != nil { if err != nil {
log.Printf("Error checking wishlist ownership: %v", err) log.Printf("Error checking wishlist access: %v", err)
sendErrorWithCORS(w, fmt.Sprintf("Error checking wishlist ownership: %v", err), http.StatusInternalServerError) sendErrorWithCORS(w, fmt.Sprintf("Error checking wishlist access: %v", err), http.StatusInternalServerError)
return
}
if !hasAccess {
sendErrorWithCORS(w, "Access denied", http.StatusForbidden)
return return
} }
@@ -10519,8 +10715,8 @@ func (a *App) uploadWishlistImageHandler(w http.ResponseWriter, r *http.Request)
_, err = a.DB.Exec(` _, err = a.DB.Exec(`
UPDATE wishlist_items UPDATE wishlist_items
SET image_path = $1, updated_at = NOW() SET image_path = $1, updated_at = NOW()
WHERE id = $2 AND user_id = $3 WHERE id = $2
`, imagePath, wishlistID, userID) `, imagePath, wishlistID)
if err != nil { if err != nil {
log.Printf("Error updating database: %v", err) log.Printf("Error updating database: %v", err)
sendErrorWithCORS(w, "Error updating database", http.StatusInternalServerError) sendErrorWithCORS(w, "Error updating database", http.StatusInternalServerError)
@@ -10555,27 +10751,27 @@ func (a *App) completeWishlistHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
// Проверяем владельца // Проверяем доступ к желанию
var ownerID int hasAccess, _, _, err := a.checkWishlistAccess(itemID, userID)
err = a.DB.QueryRow(` if err == sql.ErrNoRows {
SELECT user_id FROM wishlist_items
WHERE id = $1 AND deleted = FALSE
`, itemID).Scan(&ownerID)
if err == sql.ErrNoRows || ownerID != userID {
sendErrorWithCORS(w, "Wishlist item not found", http.StatusNotFound) sendErrorWithCORS(w, "Wishlist item not found", http.StatusNotFound)
return return
} }
if err != nil { if err != nil {
log.Printf("Error checking wishlist ownership: %v", err) log.Printf("Error checking wishlist access: %v", err)
sendErrorWithCORS(w, fmt.Sprintf("Error checking wishlist ownership: %v", err), http.StatusInternalServerError) sendErrorWithCORS(w, fmt.Sprintf("Error checking wishlist access: %v", err), http.StatusInternalServerError)
return
}
if !hasAccess {
sendErrorWithCORS(w, "Access denied", http.StatusForbidden)
return return
} }
_, err = a.DB.Exec(` _, err = a.DB.Exec(`
UPDATE wishlist_items UPDATE wishlist_items
SET completed = TRUE, updated_at = NOW() SET completed = TRUE, updated_at = NOW()
WHERE id = $1 AND user_id = $2 WHERE id = $1
`, itemID, userID) `, itemID)
if err != nil { if err != nil {
log.Printf("Error completing wishlist item: %v", err) log.Printf("Error completing wishlist item: %v", err)
@@ -10685,27 +10881,27 @@ func (a *App) uncompleteWishlistHandler(w http.ResponseWriter, r *http.Request)
return return
} }
// Проверяем владельца // Проверяем доступ к желанию
var ownerID int hasAccess, _, _, err := a.checkWishlistAccess(itemID, userID)
err = a.DB.QueryRow(` if err == sql.ErrNoRows {
SELECT user_id FROM wishlist_items
WHERE id = $1 AND deleted = FALSE
`, itemID).Scan(&ownerID)
if err == sql.ErrNoRows || ownerID != userID {
sendErrorWithCORS(w, "Wishlist item not found", http.StatusNotFound) sendErrorWithCORS(w, "Wishlist item not found", http.StatusNotFound)
return return
} }
if err != nil { if err != nil {
log.Printf("Error checking wishlist ownership: %v", err) log.Printf("Error checking wishlist access: %v", err)
sendErrorWithCORS(w, fmt.Sprintf("Error checking wishlist ownership: %v", err), http.StatusInternalServerError) sendErrorWithCORS(w, fmt.Sprintf("Error checking wishlist access: %v", err), http.StatusInternalServerError)
return
}
if !hasAccess {
sendErrorWithCORS(w, "Access denied", http.StatusForbidden)
return return
} }
_, err = a.DB.Exec(` _, err = a.DB.Exec(`
UPDATE wishlist_items UPDATE wishlist_items
SET completed = FALSE, updated_at = NOW() SET completed = FALSE, updated_at = NOW()
WHERE id = $1 AND user_id = $2 WHERE id = $1
`, itemID, userID) `, itemID)
if err != nil { if err != nil {
log.Printf("Error uncompleting wishlist item: %v", err) log.Printf("Error uncompleting wishlist item: %v", err)

View File

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

View File

@@ -914,6 +914,7 @@ function AppContent() {
key={tabParams.wishlistId} key={tabParams.wishlistId}
onNavigate={handleNavigate} onNavigate={handleNavigate}
wishlistId={tabParams.wishlistId} wishlistId={tabParams.wishlistId}
boardId={tabParams.boardId}
onRefresh={() => setWishlistRefreshTrigger(prev => prev + 1)} onRefresh={() => setWishlistRefreshTrigger(prev => prev + 1)}
/> />
</div> </div>

View File

@@ -8,7 +8,7 @@ import './TaskList.css'
const API_URL = '/api/wishlist' const API_URL = '/api/wishlist'
function WishlistDetail({ wishlistId, onNavigate, onRefresh }) { function WishlistDetail({ wishlistId, onNavigate, onRefresh, boardId }) {
const { authFetch, user } = useAuth() const { authFetch, user } = useAuth()
const [wishlistItem, setWishlistItem] = useState(null) const [wishlistItem, setWishlistItem] = useState(null)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
@@ -51,7 +51,7 @@ function WishlistDetail({ wishlistId, onNavigate, onRefresh }) {
}, [wishlistId, fetchWishlistDetail]) }, [wishlistId, fetchWishlistDetail])
const handleEdit = () => { const handleEdit = () => {
onNavigate?.('wishlist-form', { wishlistId: wishlistId }) onNavigate?.('wishlist-form', { wishlistId: wishlistId, boardId: boardId })
} }
const handleComplete = async () => { const handleComplete = async () => {

View File

@@ -31,6 +31,7 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
const [loadingWishlist, setLoadingWishlist] = useState(false) const [loadingWishlist, setLoadingWishlist] = useState(false)
const [fetchingMetadata, setFetchingMetadata] = useState(false) const [fetchingMetadata, setFetchingMetadata] = useState(false)
const [restoredFromSession, setRestoredFromSession] = useState(false) // Флаг восстановления из sessionStorage const [restoredFromSession, setRestoredFromSession] = useState(false) // Флаг восстановления из sessionStorage
const [loadedWishlistData, setLoadedWishlistData] = useState(null) // Данные желания для последующего маппинга условий
const fileInputRef = useRef(null) const fileInputRef = useRef(null)
// Загрузка задач и проектов // Загрузка задач и проектов
@@ -65,13 +66,41 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
return return
} }
if (wishlistId !== undefined && wishlistId !== null && tasks.length > 0 && projects.length > 0) { if (wishlistId !== undefined && wishlistId !== null) {
// Загружаем желание независимо от наличия задач и проектов
loadWishlist() loadWishlist()
} else if (wishlistId === undefined || wishlistId === null) { } else if (wishlistId === undefined || wishlistId === null) {
// Сбрасываем форму при создании новой задачи // Сбрасываем форму при создании новой задачи
resetForm() resetForm()
setLoadedWishlistData(null)
} }
}, [wishlistId, tasks, projects, restoredFromSession]) }, [wishlistId, restoredFromSession])
// Обновляем маппинг условий после загрузки задач и проектов
useEffect(() => {
// Если есть загруженные данные желания, но маппинг еще не выполнен,
// обновляем условия с правильным маппингом
if (loadedWishlistData && tasks.length > 0 && projects.length > 0) {
const data = loadedWishlistData
setName(data.name || '')
setPrice(data.price ? String(data.price) : '')
setLink(data.link || '')
setImageUrl(data.image_url || null)
if (data.unlock_conditions) {
setUnlockConditions(data.unlock_conditions.map((cond, idx) => ({
type: cond.type,
task_id: cond.type === 'task_completion' ? 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,
required_points: cond.required_points || null,
start_date: cond.start_date || null,
display_order: idx,
})))
} else {
setUnlockConditions([])
}
setLoadedWishlistData(null) // Очищаем после применения
}
}, [tasks, projects, loadedWishlistData])
// Сброс формы при размонтировании компонента или при изменении wishlistId на undefined // Сброс формы при размонтировании компонента или при изменении wishlistId на undefined
useEffect(() => { useEffect(() => {
@@ -199,6 +228,10 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
throw new Error('Ошибка загрузки желания') throw new Error('Ошибка загрузки желания')
} }
const data = await response.json() const data = await response.json()
// Если задачи и проекты уже загружены, применяем данные сразу
// Иначе сохраняем данные для последующего применения
if (tasks.length > 0 && projects.length > 0) {
setName(data.name || '') setName(data.name || '')
setPrice(data.price ? String(data.price) : '') setPrice(data.price ? String(data.price) : '')
setLink(data.link || '') setLink(data.link || '')
@@ -216,6 +249,16 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
} else { } else {
setUnlockConditions([]) setUnlockConditions([])
} }
} else {
// Сохраняем данные для последующего применения после загрузки задач и проектов
setLoadedWishlistData(data)
// Применяем базовые данные сразу
setName(data.name || '')
setPrice(data.price ? String(data.price) : '')
setLink(data.link || '')
setImageUrl(data.image_url || null)
setImageFile(null)
}
} catch (err) { } catch (err) {
setError(err.message) setError(err.message)
} finally { } finally {