4.15.0: Добавлена принадлежность желаний к проектам
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m33s

This commit is contained in:
poignatov
2026-02-04 15:46:05 +03:00
parent b9482dc86d
commit c22e56e68a
8 changed files with 252 additions and 18 deletions

View File

@@ -1 +1 @@
4.14.1 4.15.0

View File

@@ -378,6 +378,8 @@ type WishlistItem struct {
UnlockConditions []UnlockConditionDisplay `json:"unlock_conditions,omitempty"` UnlockConditions []UnlockConditionDisplay `json:"unlock_conditions,omitempty"`
LinkedTask *LinkedTask `json:"linked_task,omitempty"` LinkedTask *LinkedTask `json:"linked_task,omitempty"`
TasksCount int `json:"tasks_count,omitempty"` // Количество задач для этого желания TasksCount int `json:"tasks_count,omitempty"` // Количество задач для этого желания
ProjectID *int `json:"project_id,omitempty"` // ID проекта, к которому принадлежит желание
ProjectName *string `json:"project_name,omitempty"` // Название проекта
} }
type UnlockConditionDisplay struct { type UnlockConditionDisplay struct {
@@ -404,6 +406,7 @@ type WishlistRequest struct {
Name string `json:"name"` Name string `json:"name"`
Price *float64 `json:"price,omitempty"` Price *float64 `json:"price,omitempty"`
Link *string `json:"link,omitempty"` Link *string `json:"link,omitempty"`
ProjectID *int `json:"project_id,omitempty"` // ID проекта, к которому принадлежит желание
UnlockConditions []UnlockConditionRequest `json:"unlock_conditions,omitempty"` UnlockConditions []UnlockConditionRequest `json:"unlock_conditions,omitempty"`
} }
@@ -9938,6 +9941,8 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool)
wi.image_path, wi.image_path,
wi.link, wi.link,
wi.completed, wi.completed,
wi.project_id AS item_project_id,
wp.name AS item_project_name,
wc.id AS condition_id, wc.id AS condition_id,
wc.display_order, wc.display_order,
wc.task_condition_id, wc.task_condition_id,
@@ -9950,6 +9955,7 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool)
sc.required_points, sc.required_points,
sc.start_date sc.start_date
FROM wishlist_items wi FROM wishlist_items wi
LEFT JOIN projects wp ON wi.project_id = wp.id AND wp.deleted = FALSE
LEFT JOIN wishlist_conditions wc ON wi.id = wc.wishlist_item_id 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 task_conditions tc ON wc.task_condition_id = tc.id
LEFT JOIN tasks t ON tc.task_id = t.id AND t.deleted = FALSE LEFT JOIN tasks t ON tc.task_id = t.id AND t.deleted = FALSE
@@ -9976,6 +9982,8 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool)
var price sql.NullFloat64 var price sql.NullFloat64
var imagePath, link sql.NullString var imagePath, link sql.NullString
var completed bool var completed bool
var itemProjectID sql.NullInt64
var itemProjectName sql.NullString
var conditionID, displayOrder sql.NullInt64 var conditionID, displayOrder sql.NullInt64
var taskConditionID, scoreConditionID sql.NullInt64 var taskConditionID, scoreConditionID sql.NullInt64
@@ -9989,6 +9997,7 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool)
err := rows.Scan( err := rows.Scan(
&itemID, &name, &price, &imagePath, &link, &completed, &itemID, &name, &price, &imagePath, &link, &completed,
&itemProjectID, &itemProjectName,
&conditionID, &displayOrder, &conditionID, &displayOrder,
&taskConditionID, &scoreConditionID, &conditionUserID, &taskConditionID, &scoreConditionID, &conditionUserID,
&taskID, &taskName, &taskID, &taskName,
@@ -10019,6 +10028,14 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool)
l := link.String l := link.String
item.Link = &l item.Link = &l
} }
if itemProjectID.Valid {
projectIDVal := int(itemProjectID.Int64)
item.ProjectID = &projectIDVal
}
if itemProjectName.Valid {
projectNameVal := itemProjectName.String
item.ProjectName = &projectNameVal
}
itemsMap[itemID] = item itemsMap[itemID] = item
} }
@@ -10681,10 +10698,10 @@ func (a *App) createWishlistHandler(w http.ResponseWriter, r *http.Request) {
var wishlistID int var wishlistID int
err = tx.QueryRow(` err = tx.QueryRow(`
INSERT INTO wishlist_items (user_id, author_id, name, price, link, completed, deleted) INSERT INTO wishlist_items (user_id, author_id, name, price, link, project_id, completed, deleted)
VALUES ($1, $1, $2, $3, $4, FALSE, FALSE) VALUES ($1, $1, $2, $3, $4, $5, FALSE, FALSE)
RETURNING id RETURNING id
`, userID, strings.TrimSpace(req.Name), req.Price, req.Link).Scan(&wishlistID) `, userID, strings.TrimSpace(req.Name), req.Price, req.Link, req.ProjectID).Scan(&wishlistID)
if err != nil { if err != nil {
log.Printf("Error creating wishlist item: %v", err) log.Printf("Error creating wishlist item: %v", err)
@@ -10895,6 +10912,8 @@ func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) {
wi.image_path, wi.image_path,
wi.link, wi.link,
wi.completed, wi.completed,
wi.project_id AS item_project_id,
wp.name AS item_project_name,
wc.id AS condition_id, wc.id AS condition_id,
wc.display_order, wc.display_order,
wc.task_condition_id, wc.task_condition_id,
@@ -10907,6 +10926,7 @@ func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) {
sc.required_points, sc.required_points,
sc.start_date sc.start_date
FROM wishlist_items wi FROM wishlist_items wi
LEFT JOIN projects wp ON wi.project_id = wp.id AND wp.deleted = FALSE
LEFT JOIN wishlist_conditions wc ON wi.id = wc.wishlist_item_id 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 task_conditions tc ON wc.task_condition_id = tc.id
LEFT JOIN tasks t ON tc.task_id = t.id AND t.deleted = FALSE LEFT JOIN tasks t ON tc.task_id = t.id AND t.deleted = FALSE
@@ -10933,6 +10953,8 @@ func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) {
var imagePath sql.NullString var imagePath sql.NullString
var link sql.NullString var link sql.NullString
var completed bool var completed bool
var itemProjectID sql.NullInt64
var itemProjectName sql.NullString
var conditionID sql.NullInt64 var conditionID sql.NullInt64
var displayOrder sql.NullInt64 var displayOrder sql.NullInt64
var taskConditionID sql.NullInt64 var taskConditionID sql.NullInt64
@@ -10946,7 +10968,7 @@ func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) {
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, &itemProjectID, &itemProjectName,
&conditionID, &displayOrder, &taskConditionID, &scoreConditionID, &conditionUserID, &conditionID, &displayOrder, &taskConditionID, &scoreConditionID, &conditionUserID,
&taskID, &taskName, &projectID, &projectName, &requiredPoints, &startDate, &taskID, &taskName, &projectID, &projectName, &requiredPoints, &startDate,
) )
@@ -10976,6 +10998,14 @@ func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) {
if link.Valid { if link.Valid {
item.Link = &link.String item.Link = &link.String
} }
if itemProjectID.Valid {
projectIDVal := int(itemProjectID.Int64)
item.ProjectID = &projectIDVal
}
if itemProjectName.Valid {
projectNameVal := itemProjectName.String
item.ProjectName = &projectNameVal
}
itemsMap[itemID] = item itemsMap[itemID] = item
} }
@@ -11231,9 +11261,9 @@ func (a *App) updateWishlistHandler(w http.ResponseWriter, r *http.Request) {
// Обновляем желание (не проверяем user_id в WHERE, так как доступ уже проверен выше) // Обновляем желание (не проверяем 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, project_id = $4, updated_at = NOW()
WHERE id = $4 WHERE id = $5
`, strings.TrimSpace(req.Name), req.Price, req.Link, itemID) `, strings.TrimSpace(req.Name), req.Price, req.Link, req.ProjectID, itemID)
if err != nil { if err != nil {
log.Printf("Error updating wishlist item: %v", err) log.Printf("Error updating wishlist item: %v", err)
@@ -13014,6 +13044,8 @@ func (a *App) getBoardCompletedHandler(w http.ResponseWriter, r *http.Request) {
wi.image_path, wi.image_path,
wi.link, wi.link,
wi.completed, wi.completed,
wi.project_id AS item_project_id,
wp.name AS item_project_name,
wc.id AS condition_id, wc.id AS condition_id,
wc.display_order, wc.display_order,
wc.task_condition_id, wc.task_condition_id,
@@ -13027,6 +13059,7 @@ func (a *App) getBoardCompletedHandler(w http.ResponseWriter, r *http.Request) {
sc.start_date, sc.start_date,
COALESCE(u.name, u.email) AS user_name COALESCE(u.name, u.email) AS user_name
FROM wishlist_items wi FROM wishlist_items wi
LEFT JOIN projects wp ON wi.project_id = wp.id AND wp.deleted = FALSE
LEFT JOIN wishlist_conditions wc ON wi.id = wc.wishlist_item_id 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 task_conditions tc ON wc.task_condition_id = tc.id
LEFT JOIN tasks t ON tc.task_id = t.id AND t.deleted = FALSE LEFT JOIN tasks t ON tc.task_id = t.id AND t.deleted = FALSE
@@ -13056,6 +13089,8 @@ func (a *App) getBoardCompletedHandler(w http.ResponseWriter, r *http.Request) {
var imagePath sql.NullString var imagePath sql.NullString
var link sql.NullString var link sql.NullString
var completed bool var completed bool
var itemProjectID sql.NullInt64
var itemProjectName sql.NullString
var conditionID sql.NullInt64 var conditionID sql.NullInt64
var displayOrder sql.NullInt64 var displayOrder sql.NullInt64
var taskConditionID sql.NullInt64 var taskConditionID sql.NullInt64
@@ -13070,7 +13105,7 @@ func (a *App) getBoardCompletedHandler(w http.ResponseWriter, r *http.Request) {
var userName sql.NullString var userName sql.NullString
err := rows.Scan( err := rows.Scan(
&itemID, &name, &price, &imagePath, &link, &completed, &itemID, &name, &price, &imagePath, &link, &completed, &itemProjectID, &itemProjectName,
&conditionID, &displayOrder, &taskConditionID, &scoreConditionID, &userIDCond, &conditionID, &displayOrder, &taskConditionID, &scoreConditionID, &userIDCond,
&taskID, &taskName, &projectID, &projectName, &requiredPoints, &startDate, &userName, &taskID, &taskName, &projectID, &projectName, &requiredPoints, &startDate, &userName,
) )
@@ -13100,6 +13135,8 @@ func (a *App) getBoardCompletedHandler(w http.ResponseWriter, r *http.Request) {
if link.Valid { if link.Valid {
item.Link = &link.String item.Link = &link.String
} }
// Для завершённых желаний не устанавливаем project_id и project_name
// Они отображаются отдельно без группировки по проектам
itemsMap[itemID] = item itemsMap[itemID] = item
} }
@@ -13305,6 +13342,8 @@ func (a *App) getWishlistItemsByBoard(boardID int, userID int) ([]WishlistItem,
wi.image_path, wi.image_path,
wi.link, wi.link,
wi.completed, wi.completed,
wi.project_id AS item_project_id,
wp.name AS item_project_name,
COALESCE(wi.author_id, wi.user_id) AS item_owner_id, 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,
@@ -13318,6 +13357,7 @@ func (a *App) getWishlistItemsByBoard(boardID int, userID int) ([]WishlistItem,
sc.required_points, sc.required_points,
sc.start_date sc.start_date
FROM wishlist_items wi FROM wishlist_items wi
LEFT JOIN projects wp ON wi.project_id = wp.id AND wp.deleted = FALSE
LEFT JOIN wishlist_conditions wc ON wi.id = wc.wishlist_item_id 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 task_conditions tc ON wc.task_condition_id = tc.id
LEFT JOIN tasks t ON tc.task_id = t.id AND t.deleted = FALSE LEFT JOIN tasks t ON tc.task_id = t.id AND t.deleted = FALSE
@@ -13344,6 +13384,8 @@ 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 itemProjectID sql.NullInt64
var itemProjectName sql.NullString
var itemOwnerID sql.NullInt64 var itemOwnerID sql.NullInt64
var conditionID sql.NullInt64 var conditionID sql.NullInt64
var displayOrder sql.NullInt64 var displayOrder sql.NullInt64
@@ -13358,7 +13400,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, &itemOwnerID, &itemID, &name, &price, &imagePath, &link, &completed, &itemProjectID, &itemProjectName, &itemOwnerID,
&conditionID, &displayOrder, &taskConditionID, &scoreConditionID, &conditionUserID, &conditionID, &displayOrder, &taskConditionID, &scoreConditionID, &conditionUserID,
&taskID, &taskName, &projectID, &projectName, &requiredPoints, &startDate, &taskID, &taskName, &projectID, &projectName, &requiredPoints, &startDate,
) )
@@ -13388,6 +13430,14 @@ func (a *App) getWishlistItemsByBoard(boardID int, userID int) ([]WishlistItem,
if link.Valid { if link.Valid {
item.Link = &link.String item.Link = &link.String
} }
if itemProjectID.Valid {
projectIDVal := int(itemProjectID.Int64)
item.ProjectID = &projectIDVal
}
if itemProjectName.Valid {
projectNameVal := itemProjectName.String
item.ProjectName = &projectNameVal
}
itemsMap[itemID] = item itemsMap[itemID] = item
} }
@@ -13657,10 +13707,10 @@ func (a *App) createBoardItemHandler(w http.ResponseWriter, r *http.Request) {
var itemID int var itemID int
err = tx.QueryRow(` err = tx.QueryRow(`
INSERT INTO wishlist_items (user_id, board_id, author_id, name, price, link, completed, deleted) INSERT INTO wishlist_items (user_id, board_id, author_id, name, price, link, project_id, completed, deleted)
VALUES ($1, $2, $3, $4, $5, $6, FALSE, FALSE) VALUES ($1, $2, $3, $4, $5, $6, $7, FALSE, FALSE)
RETURNING id RETURNING id
`, ownerID, boardID, userID, strings.TrimSpace(req.Name), req.Price, req.Link).Scan(&itemID) `, ownerID, boardID, userID, strings.TrimSpace(req.Name), req.Price, req.Link, req.ProjectID).Scan(&itemID)
if err != nil { if err != nil {
log.Printf("createBoardItemHandler: Error creating board item: %v", err) log.Printf("createBoardItemHandler: Error creating board item: %v", err)

View File

@@ -0,0 +1,9 @@
-- Migration: Remove project_id field from wishlist_items table
-- Date: 2026-02-02
--
-- This migration reverts the addition of project_id field.
DROP INDEX IF EXISTS idx_wishlist_items_project_id;
ALTER TABLE wishlist_items
DROP COLUMN IF EXISTS project_id;

View File

@@ -0,0 +1,13 @@
-- Migration: Add project_id field to wishlist_items table
-- Date: 2026-02-02
--
-- This migration adds project_id field to wishlist_items table to allow
-- grouping wishlist items by project. The field is nullable, so existing
-- items without a project will remain valid.
ALTER TABLE wishlist_items
ADD COLUMN project_id INTEGER REFERENCES projects(id) ON DELETE SET NULL;
CREATE INDEX idx_wishlist_items_project_id ON wishlist_items(project_id);
COMMENT ON COLUMN wishlist_items.project_id IS 'Project this wishlist item belongs to (optional)';

View File

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

View File

@@ -93,6 +93,53 @@
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.wishlist-project-group {
margin-bottom: 2rem;
}
.wishlist-project-group-title {
font-size: 1.25rem;
font-weight: 600;
color: #2c3e50;
margin-bottom: 0.75rem;
}
.wishlist-project-group-items {
display: flex;
overflow-x: auto;
gap: 1rem;
padding-bottom: 0.5rem;
-webkit-overflow-scrolling: touch;
scrollbar-width: thin;
}
.wishlist-project-group-items::-webkit-scrollbar {
height: 8px;
}
.wishlist-project-group-items::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.wishlist-project-group-items::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
.wishlist-project-group-items::-webkit-scrollbar-thumb:hover {
background: #555;
}
.wishlist-project-group-items .wishlist-card {
flex: 0 0 150px;
min-width: 150px;
}
.wishlist-no-project {
margin-top: 1.5rem;
}
.wishlist-card { .wishlist-card {
overflow: hidden; overflow: hidden;
cursor: pointer; cursor: pointer;

View File

@@ -1,8 +1,9 @@
import React, { useState, useEffect, useRef } from 'react' import React, { useState, useEffect, useRef, useMemo } from 'react'
import { useAuth } from './auth/AuthContext' import { useAuth } from './auth/AuthContext'
import BoardSelector from './BoardSelector' import BoardSelector from './BoardSelector'
import LoadingError from './LoadingError' import LoadingError from './LoadingError'
import WishlistDetail from './WishlistDetail' import WishlistDetail from './WishlistDetail'
import { sortProjectsLikeCurrentWeek } from '../utils/projectUtils'
import './Wishlist.css' import './Wishlist.css'
const API_URL = '/api/wishlist' const API_URL = '/api/wishlist'
@@ -45,6 +46,7 @@ function Wishlist({ onNavigate, refreshTrigger = 0, isActive = false, initialBoa
const [completedLoading, setCompletedLoading] = useState(false) const [completedLoading, setCompletedLoading] = useState(false)
const [selectedItem, setSelectedItem] = useState(null) const [selectedItem, setSelectedItem] = useState(null)
const [selectedWishlistForDetail, setSelectedWishlistForDetail] = useState(null) const [selectedWishlistForDetail, setSelectedWishlistForDetail] = useState(null)
const [currentWeekData, setCurrentWeekData] = useState(null)
const fetchingRef = useRef(false) const fetchingRef = useRef(false)
const fetchingCompletedRef = useRef(false) const fetchingCompletedRef = useRef(false)
const initialFetchDoneRef = useRef(false) const initialFetchDoneRef = useRef(false)
@@ -234,6 +236,24 @@ function Wishlist({ onNavigate, refreshTrigger = 0, isActive = false, initialBoa
} }
} }
// Загрузка данных текущей недели для сортировки проектов
const fetchCurrentWeek = async () => {
try {
const response = await authFetch('/api/current-week')
if (response.ok) {
const data = await response.json()
// Обрабатываем ответ: приходит массив с одним объектом [{total: ..., projects: [...]}]
if (Array.isArray(data) && data.length > 0) {
setCurrentWeekData(data[0])
} else if (data && typeof data === 'object') {
setCurrentWeekData(data)
}
}
} catch (err) {
console.error('Error loading current week data:', err)
}
}
// Первая инициализация // Первая инициализация
useEffect(() => { useEffect(() => {
if (!initialFetchDoneRef.current) { if (!initialFetchDoneRef.current) {
@@ -247,6 +267,9 @@ function Wishlist({ onNavigate, refreshTrigger = 0, isActive = false, initialBoa
// Загружаем доски с сервера // Загружаем доски с сервера
fetchBoards() fetchBoards()
// Загружаем данные текущей недели для сортировки проектов
fetchCurrentWeek()
} }
}, []) }, [])
@@ -557,6 +580,60 @@ function Wishlist({ onNavigate, refreshTrigger = 0, isActive = false, initialBoa
) )
} }
// Группируем желания по проектам
const groupedItems = useMemo(() => {
const groups = {}
const noProjectItems = []
items.forEach(item => {
if (item.project_id && item.project_name) {
const projectId = item.project_id
if (!groups[projectId]) {
groups[projectId] = {
projectId: projectId,
projectName: item.project_name,
items: []
}
}
groups[projectId].items.push(item)
} else {
noProjectItems.push(item)
}
})
// Сортируем группы проектов
const projectIds = Object.keys(groups)
if (currentWeekData && projectIds.length > 0) {
const projectNames = projectIds.map(id => groups[id].projectName)
const sortedProjectNames = sortProjectsLikeCurrentWeek(projectNames, currentWeekData)
// Создаем отсортированный массив групп
const sortedGroups = []
sortedProjectNames.forEach(projectName => {
const group = Object.values(groups).find(g => g.projectName === projectName)
if (group) {
sortedGroups.push(group)
}
})
// Добавляем группы, которых нет в currentWeekData (если есть)
Object.values(groups).forEach(group => {
if (!sortedProjectNames.includes(group.projectName)) {
sortedGroups.push(group)
}
})
return { groups: sortedGroups, noProjectItems }
}
// Если нет данных текущей недели, сортируем по алфавиту
const sortedGroups = Object.values(groups).sort((a, b) =>
a.projectName.localeCompare(b.projectName)
)
return { groups: sortedGroups, noProjectItems }
}, [items, currentWeekData])
const renderItem = (item) => { const renderItem = (item) => {
const isFaded = (!item.unlocked && !item.completed) || item.completed const isFaded = (!item.unlocked && !item.completed) || item.completed
@@ -646,9 +723,24 @@ function Wishlist({ onNavigate, refreshTrigger = 0, isActive = false, initialBoa
</div> </div>
) : ( ) : (
<> <>
<div className="wishlist-grid"> {/* Группы проектов */}
{items.map(renderItem)} {groupedItems.groups.map(group => (
<div key={group.projectId} className="wishlist-project-group">
<div className="wishlist-project-group-title">{group.projectName}</div>
<div className="wishlist-project-group-items">
{group.items.map(renderItem)}
</div> </div>
</div>
))}
{/* Желания без проекта */}
{groupedItems.noProjectItems.length > 0 && (
<div className="wishlist-no-project">
<div className="wishlist-grid">
{groupedItems.noProjectItems.map(renderItem)}
</div>
</div>
)}
{/* Завершённые */} {/* Завершённые */}
{completedCount > 0 && ( {completedCount > 0 && (

View File

@@ -25,6 +25,7 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
const [editingConditionIndex, setEditingConditionIndex] = useState(null) const [editingConditionIndex, setEditingConditionIndex] = useState(null)
const [tasks, setTasks] = useState([]) const [tasks, setTasks] = useState([])
const [projects, setProjects] = useState([]) const [projects, setProjects] = useState([])
const [selectedProjectId, setSelectedProjectId] = useState('')
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [error, setError] = useState('') const [error, setError] = useState('')
const [toastMessage, setToastMessage] = useState(null) const [toastMessage, setToastMessage] = useState(null)
@@ -86,6 +87,7 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
setPrice(data.price ? String(data.price) : '') setPrice(data.price ? String(data.price) : '')
setLink(data.link || '') setLink(data.link || '')
setImageUrl(data.image_url || null) setImageUrl(data.image_url || null)
setSelectedProjectId(data.project_id ? String(data.project_id) : '')
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, id: cond.id || null,
@@ -242,6 +244,7 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
setLink(data.link || '') setLink(data.link || '')
setImageUrl(data.image_url || null) setImageUrl(data.image_url || null)
setImageFile(null) // Сбрасываем imageFile при загрузке существующего желания setImageFile(null) // Сбрасываем imageFile при загрузке существующего желания
setSelectedProjectId(data.project_id ? String(data.project_id) : '')
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, id: cond.id || null,
@@ -268,6 +271,7 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
setLink(data.link || '') setLink(data.link || '')
setImageUrl(data.image_url || null) setImageUrl(data.image_url || null)
setImageFile(null) setImageFile(null)
setSelectedProjectId(data.project_id ? String(data.project_id) : '')
} }
} catch (err) { } catch (err) {
setError(err.message) setError(err.message)
@@ -283,6 +287,7 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
setImageUrl(null) setImageUrl(null)
setImageFile(null) setImageFile(null)
setUnlockConditions([]) setUnlockConditions([])
setSelectedProjectId('')
setError('') setError('')
setShowCropper(false) setShowCropper(false)
setCrop({ x: 0, y: 0 }) setCrop({ x: 0, y: 0 })
@@ -553,6 +558,7 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
name: name.trim(), name: name.trim(),
price: price ? parseFloat(price) : null, price: price ? parseFloat(price) : null,
link: link.trim() || null, link: link.trim() || null,
project_id: selectedProjectId ? parseInt(selectedProjectId, 10) : null,
unlock_conditions: unlockConditions.map(cond => ({ unlock_conditions: unlockConditions.map(cond => ({
id: cond.id || null, id: cond.id || null,
type: cond.type, type: cond.type,
@@ -841,6 +847,23 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
</button> </button>
</div> </div>
<div className="form-group">
<label htmlFor="project">Принадлежность к проекту</label>
<select
id="project"
value={selectedProjectId}
onChange={(e) => setSelectedProjectId(e.target.value)}
className="form-input"
>
<option value="">Не выбран</option>
{projects.map(project => (
<option key={project.project_id} value={project.project_id}>
{project.project_name}
</option>
))}
</select>
</div>
{error && <div className="error-message">{error}</div>} {error && <div className="error-message">{error}</div>}
<div className="form-actions"> <div className="form-actions">