From e962f4940790ea1592542997b82dbf37b99ba47f Mon Sep 17 00:00:00 2001 From: poignatov Date: Tue, 10 Mar 2026 16:39:23 +0300 Subject: [PATCH] =?UTF-8?q?6.7.0:=20=D0=9E=D0=BF=D0=B8=D1=81=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D1=82=D0=BE=D0=B2=D0=B0=D1=80=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=B8=20=D1=80=D0=B5=D0=B4=D0=B8=D0=B7=D0=B0=D0=B9=D0=BD=20?= =?UTF-8?q?=D0=B2=D1=8B=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- VERSION | 2 +- play-life-backend/main.go | 31 ++++-- ...027_add_shopping_item_description.down.sql | 1 + ...00027_add_shopping_item_description.up.sql | 1 + play-life-web/package.json | 2 +- .../src/components/ShoppingItemDetail.jsx | 69 +++++++++---- .../src/components/ShoppingItemForm.jsx | 15 +++ play-life-web/src/components/ShoppingList.jsx | 4 +- play-life-web/src/components/TaskDetail.css | 97 +++++++++++++++++++ 9 files changed, 190 insertions(+), 32 deletions(-) create mode 100644 play-life-backend/migrations/000027_add_shopping_item_description.down.sql create mode 100644 play-life-backend/migrations/000027_add_shopping_item_description.up.sql diff --git a/VERSION b/VERSION index b22e754..f0e13c5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.6.4 +6.7.0 diff --git a/play-life-backend/main.go b/play-life-backend/main.go index 1866283..25d643b 100644 --- a/play-life-backend/main.go +++ b/play-life-backend/main.go @@ -17732,6 +17732,7 @@ type ShoppingItem struct { BoardID int `json:"board_id"` AuthorID int `json:"author_id"` Name string `json:"name"` + Description *string `json:"description,omitempty"` GroupName *string `json:"group_name,omitempty"` VolumeBase float64 `json:"volume_base"` RepetitionPeriod *string `json:"repetition_period,omitempty"` @@ -17743,6 +17744,7 @@ type ShoppingItem struct { type ShoppingItemRequest struct { Name string `json:"name"` + Description *string `json:"description,omitempty"` GroupName *string `json:"group_name,omitempty"` VolumeBase *float64 `json:"volume_base,omitempty"` RepetitionPeriod *string `json:"repetition_period,omitempty"` @@ -18487,7 +18489,7 @@ func (a *App) getShoppingItemsHandler(w http.ResponseWriter, r *http.Request) { items := []ShoppingItem{} rows, err := a.DB.Query(` SELECT - si.id, si.user_id, si.board_id, si.author_id, si.name, si.group_name, + si.id, si.user_id, si.board_id, si.author_id, si.name, si.description, si.group_name, si.volume_base, si.repetition_period::text, si.next_show_at, si.completed, si.last_completed_at, si.created_at FROM shopping_items si @@ -18503,6 +18505,7 @@ func (a *App) getShoppingItemsHandler(w http.ResponseWriter, r *http.Request) { for rows.Next() { var item ShoppingItem + var description sql.NullString var groupName sql.NullString var repetitionPeriod sql.NullString var nextShowAt sql.NullTime @@ -18510,7 +18513,7 @@ func (a *App) getShoppingItemsHandler(w http.ResponseWriter, r *http.Request) { var createdAt time.Time err := rows.Scan( - &item.ID, &item.UserID, &item.BoardID, &item.AuthorID, &item.Name, &groupName, + &item.ID, &item.UserID, &item.BoardID, &item.AuthorID, &item.Name, &description, &groupName, &item.VolumeBase, &repetitionPeriod, &nextShowAt, &item.Completed, &lastCompletedAt, &createdAt, ) @@ -18519,6 +18522,9 @@ func (a *App) getShoppingItemsHandler(w http.ResponseWriter, r *http.Request) { continue } + if description.Valid { + item.Description = &description.String + } if groupName.Valid { item.GroupName = &groupName.String } @@ -18599,10 +18605,10 @@ func (a *App) createShoppingItemHandler(w http.ResponseWriter, r *http.Request) var itemID int err = a.DB.QueryRow(` - INSERT INTO shopping_items (user_id, board_id, author_id, name, group_name, volume_base, repetition_period) - VALUES ($1, $2, $3, $4, $5, $6, $7::interval) + INSERT INTO shopping_items (user_id, board_id, author_id, name, description, group_name, volume_base, repetition_period) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8::interval) RETURNING id - `, boardOwnerID, boardID, userID, strings.TrimSpace(req.Name), req.GroupName, volumeBase, req.RepetitionPeriod).Scan(&itemID) + `, boardOwnerID, boardID, userID, strings.TrimSpace(req.Name), req.Description, req.GroupName, volumeBase, req.RepetitionPeriod).Scan(&itemID) if err != nil { log.Printf("Error creating shopping item: %v", err) @@ -18616,6 +18622,7 @@ func (a *App) createShoppingItemHandler(w http.ResponseWriter, r *http.Request) BoardID: boardID, AuthorID: userID, Name: strings.TrimSpace(req.Name), + Description: req.Description, GroupName: req.GroupName, VolumeBase: volumeBase, RepetitionPeriod: req.RepetitionPeriod, @@ -18651,6 +18658,7 @@ func (a *App) getShoppingItemHandler(w http.ResponseWriter, r *http.Request) { } var item ShoppingItem + var description sql.NullString var groupName sql.NullString var repetitionPeriod sql.NullString var nextShowAt sql.NullTime @@ -18659,13 +18667,13 @@ func (a *App) getShoppingItemHandler(w http.ResponseWriter, r *http.Request) { err = a.DB.QueryRow(` SELECT - si.id, si.user_id, si.board_id, si.author_id, si.name, si.group_name, + si.id, si.user_id, si.board_id, si.author_id, si.name, si.description, si.group_name, si.volume_base, si.repetition_period::text, si.next_show_at, si.completed, si.last_completed_at, si.created_at FROM shopping_items si WHERE si.id = $1 AND si.deleted = FALSE `, itemID).Scan( - &item.ID, &item.UserID, &item.BoardID, &item.AuthorID, &item.Name, &groupName, + &item.ID, &item.UserID, &item.BoardID, &item.AuthorID, &item.Name, &description, &groupName, &item.VolumeBase, &repetitionPeriod, &nextShowAt, &item.Completed, &lastCompletedAt, &createdAt, ) @@ -18693,6 +18701,9 @@ func (a *App) getShoppingItemHandler(w http.ResponseWriter, r *http.Request) { } } + if description.Valid { + item.Description = &description.String + } if groupName.Valid { item.GroupName = &groupName.String } @@ -18774,9 +18785,9 @@ func (a *App) updateShoppingItemHandler(w http.ResponseWriter, r *http.Request) _, err = a.DB.Exec(` UPDATE shopping_items - SET name = $1, group_name = $2, volume_base = $3, repetition_period = $4::interval, updated_at = NOW() - WHERE id = $5 - `, strings.TrimSpace(req.Name), req.GroupName, volumeBase, req.RepetitionPeriod, itemID) + SET name = $1, description = $2, group_name = $3, volume_base = $4, repetition_period = $5::interval, updated_at = NOW() + WHERE id = $6 + `, strings.TrimSpace(req.Name), req.Description, req.GroupName, volumeBase, req.RepetitionPeriod, itemID) if err != nil { log.Printf("Error updating shopping item: %v", err) diff --git a/play-life-backend/migrations/000027_add_shopping_item_description.down.sql b/play-life-backend/migrations/000027_add_shopping_item_description.down.sql new file mode 100644 index 0000000..4317f2a --- /dev/null +++ b/play-life-backend/migrations/000027_add_shopping_item_description.down.sql @@ -0,0 +1 @@ +ALTER TABLE shopping_items DROP COLUMN description; diff --git a/play-life-backend/migrations/000027_add_shopping_item_description.up.sql b/play-life-backend/migrations/000027_add_shopping_item_description.up.sql new file mode 100644 index 0000000..60e56bd --- /dev/null +++ b/play-life-backend/migrations/000027_add_shopping_item_description.up.sql @@ -0,0 +1 @@ +ALTER TABLE shopping_items ADD COLUMN description TEXT; diff --git a/play-life-web/package.json b/play-life-web/package.json index 64353fc..a88e751 100644 --- a/play-life-web/package.json +++ b/play-life-web/package.json @@ -1,6 +1,6 @@ { "name": "play-life-web", - "version": "6.6.4", + "version": "6.7.0", "type": "module", "scripts": { "dev": "vite", diff --git a/play-life-web/src/components/ShoppingItemDetail.jsx b/play-life-web/src/components/ShoppingItemDetail.jsx index 042089a..a7b53b4 100644 --- a/play-life-web/src/components/ShoppingItemDetail.jsx +++ b/play-life-web/src/components/ShoppingItemDetail.jsx @@ -127,8 +127,44 @@ function ShoppingItemDetail({ itemId, onClose, onRefresh, onItemCompleted, onNav {!loading && !error && item && ( <> -
- + {item.description && ( +
+
+ {item.description.split(/(https?:\/\/[^\s<>"'`,;!)\]]+)/gi).map((part, i) => { + if (/^https?:\/\//i.test(part)) { + let host + try { + host = new URL(part).host.replace(/^www\./, '') + } catch { + host = 'Открыть ссылку' + } + return ( + + {host} + + ) + } + return {part} + })} +
+ +
+ )} + +
-
- -
- -
-
-
- -
-
+
)} diff --git a/play-life-web/src/components/ShoppingItemForm.jsx b/play-life-web/src/components/ShoppingItemForm.jsx index dca910c..ff74e44 100644 --- a/play-life-web/src/components/ShoppingItemForm.jsx +++ b/play-life-web/src/components/ShoppingItemForm.jsx @@ -8,6 +8,7 @@ import './ShoppingItemForm.css' function ShoppingItemForm({ onNavigate, itemId, boardId, onSaved }) { const { authFetch } = useAuth() const [name, setName] = useState('') + const [description, setDescription] = useState('') const [groupName, setGroupName] = useState('') const [groupSuggestions, setGroupSuggestions] = useState([]) const [volumeBase, setVolumeBase] = useState('') @@ -49,6 +50,7 @@ function ShoppingItemForm({ onNavigate, itemId, boardId, onSaved }) { if (res.ok) { const data = await res.json() setName(data.name) + setDescription(data.description || '') setGroupName(data.group_name || '') if (data.volume_base && data.volume_base !== 1) { setVolumeBase(data.volume_base.toString()) @@ -107,6 +109,7 @@ function ShoppingItemForm({ onNavigate, itemId, boardId, onSaved }) { const vb = volumeBase.trim() ? parseFloat(volumeBase.trim()) : null const payload = { name: name.trim(), + description: description.trim() || null, group_name: groupName.trim() || null, volume_base: vb && vb > 0 ? vb : null, repetition_period: repetitionPeriod, @@ -200,6 +203,18 @@ function ShoppingItemForm({ onNavigate, itemId, boardId, onSaved }) { /> +
+ +