From f3a7d1c503c533e684feb3c1ffc19bd18a4b6f68 Mon Sep 17 00:00:00 2001 From: poignatov Date: Sun, 11 Jan 2026 15:09:32 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D1=80=D0=B0=D1=81=D1=87=D0=B5=D1=82=20?= =?UTF-8?q?=D0=B4=D0=B0=D1=82=D1=8B=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=D0=B0=20=D0=B7=D0=B0=D0=B4=D0=B0=D1=87=20=D1=81=20=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B8=D0=BE=D0=B4=D0=B0=D0=BC=D0=B8=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B2=D1=82=D0=BE=D1=80=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлена поддержка сокращенных форм единиц времени (mons, min, hrs, wks, yrs и т.д.) - Исправлена обработка недель, которые PostgreSQL возвращает как дни (7 days вместо 1 week) - Добавлено приведение repetition_period к тексту при чтении из БД - Обновлена версия до 3.8.6 --- VERSION | 2 +- play-life-backend/main.go | 34 +++++++++++++++------ play-life-web/package.json | 2 +- play-life-web/src/components/TaskDetail.jsx | 20 +++++++++++- play-life-web/src/components/TaskList.jsx | 19 +++++++++++- 5 files changed, 64 insertions(+), 13 deletions(-) diff --git a/VERSION b/VERSION index 0cbfaed..2e14a95 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.8.5 +3.8.6 diff --git a/play-life-backend/main.go b/play-life-backend/main.go index 92fdd7c..6124acc 100644 --- a/play-life-backend/main.go +++ b/play-life-backend/main.go @@ -374,7 +374,8 @@ func calculateNextShowAtFromRepetitionDate(repetitionDate string, fromDate time. } // calculateNextShowAtFromRepetitionPeriod calculates the next show date by adding repetition_period to fromDate -// Format: PostgreSQL INTERVAL string (e.g., "1 day", "2 weeks", "3 months") +// Format: PostgreSQL INTERVAL string (e.g., "1 day", "2 weeks", "3 months" or "3 mons") +// Note: PostgreSQL may return weeks as days (e.g., "7 days" instead of "1 week") func calculateNextShowAtFromRepetitionPeriod(repetitionPeriod string, fromDate time.Time) *time.Time { if repetitionPeriod == "" { return nil @@ -382,33 +383,45 @@ func calculateNextShowAtFromRepetitionPeriod(repetitionPeriod string, fromDate t parts := strings.Fields(strings.TrimSpace(repetitionPeriod)) if len(parts) < 2 { + log.Printf("calculateNextShowAtFromRepetitionPeriod: invalid format, parts=%v", parts) return nil } value, err := strconv.Atoi(parts[0]) if err != nil { + log.Printf("calculateNextShowAtFromRepetitionPeriod: failed to parse value '%s': %v", parts[0], err) return nil } unit := strings.ToLower(parts[1]) + log.Printf("calculateNextShowAtFromRepetitionPeriod: value=%d, unit='%s'", value, unit) // Start from fromDate at midnight nextDate := time.Date(fromDate.Year(), fromDate.Month(), fromDate.Day(), 0, 0, 0, 0, fromDate.Location()) switch unit { - case "minute", "minutes": + case "minute", "minutes", "mins", "min": nextDate = nextDate.Add(time.Duration(value) * time.Minute) - case "hour", "hours": + case "hour", "hours", "hrs", "hr": nextDate = nextDate.Add(time.Duration(value) * time.Hour) case "day", "days": - nextDate = nextDate.AddDate(0, 0, value) - case "week", "weeks": + // PostgreSQL может возвращать недели как дни (например, "7 days" вместо "1 week") + // Если количество дней кратно 7, обрабатываем как недели + if value%7 == 0 && value >= 7 { + weeks := value / 7 + nextDate = nextDate.AddDate(0, 0, weeks*7) + } else { + nextDate = nextDate.AddDate(0, 0, value) + } + case "week", "weeks", "wks", "wk": nextDate = nextDate.AddDate(0, 0, value*7) - case "month", "months": + case "month", "months", "mons", "mon": nextDate = nextDate.AddDate(0, value, 0) - case "year", "years": + log.Printf("calculateNextShowAtFromRepetitionPeriod: added %d months, result=%v", value, nextDate) + case "year", "years", "yrs", "yr": nextDate = nextDate.AddDate(value, 0, 0) default: + log.Printf("calculateNextShowAtFromRepetitionPeriod: unknown unit '%s'", unit) return nil } @@ -7503,7 +7516,7 @@ func (a *App) completeTaskHandler(w http.ResponseWriter, r *http.Request) { var ownerID int err = a.DB.QueryRow(` - SELECT id, name, reward_message, progression_base, repetition_period, repetition_date, user_id + SELECT id, name, reward_message, progression_base, repetition_period::text, repetition_date, user_id FROM tasks WHERE id = $1 AND deleted = FALSE `, taskID).Scan(&task.ID, &task.Name, &rewardMessage, &progressionBase, &repetitionPeriod, &repetitionDate, &ownerID) @@ -7797,14 +7810,17 @@ func (a *App) completeTaskHandler(w http.ResponseWriter, r *http.Request) { // Обычный период: обновляем счетчик и last_completed_at, вычисляем next_show_at // next_show_at = last_completed_at + repetition_period now := time.Now() + log.Printf("Calculating next_show_at for task %d: repetition_period='%s', fromDate=%v", taskID, repetitionPeriod.String, now) nextShowAt := calculateNextShowAtFromRepetitionPeriod(repetitionPeriod.String, now) if nextShowAt != nil { + log.Printf("Calculated next_show_at for task %d: %v", taskID, *nextShowAt) _, err = a.DB.Exec(` UPDATE tasks SET completed = completed + 1, last_completed_at = NOW(), next_show_at = $2 WHERE id = $1 `, taskID, nextShowAt) } else { + log.Printf("Failed to calculate next_show_at for task %d: repetition_period='%s' returned nil", taskID, repetitionPeriod.String) // Если не удалось вычислить дату, обновляем как обычно _, err = a.DB.Exec(` UPDATE tasks @@ -7896,7 +7912,7 @@ func (a *App) completeAndDeleteTaskHandler(w http.ResponseWriter, r *http.Reques var ownerID int err = a.DB.QueryRow(` - SELECT id, name, reward_message, progression_base, repetition_period, repetition_date, user_id + SELECT id, name, reward_message, progression_base, repetition_period::text, repetition_date, user_id FROM tasks WHERE id = $1 AND deleted = FALSE `, taskID).Scan(&task.ID, &task.Name, &rewardMessage, &progressionBase, &repetitionPeriod, &repetitionDate, &ownerID) diff --git a/play-life-web/package.json b/play-life-web/package.json index 287a757..e693a6b 100644 --- a/play-life-web/package.json +++ b/play-life-web/package.json @@ -1,6 +1,6 @@ { "name": "play-life-web", - "version": "3.8.5", + "version": "3.8.6", "type": "module", "scripts": { "dev": "vite", diff --git a/play-life-web/src/components/TaskDetail.jsx b/play-life-web/src/components/TaskDetail.jsx index 586f9a1..98fa624 100644 --- a/play-life-web/src/components/TaskDetail.jsx +++ b/play-life-web/src/components/TaskDetail.jsx @@ -99,6 +99,7 @@ const calculateNextDateFromRepetitionDate = (repetitionDateStr) => { } // Функция для вычисления следующей даты по repetition_period +// Поддерживает сокращенные формы единиц времени (например, "mons" для месяцев) const calculateNextDateFromRepetitionPeriod = (repetitionPeriodStr) => { if (!repetitionPeriodStr) return null @@ -117,26 +118,43 @@ const calculateNextDateFromRepetitionPeriod = (repetitionPeriodStr) => { switch (unit) { case 'minute': case 'minutes': + case 'mins': + case 'min': nextDate.setMinutes(nextDate.getMinutes() + value) break case 'hour': case 'hours': + case 'hrs': + case 'hr': nextDate.setHours(nextDate.getHours() + value) break case 'day': case 'days': - nextDate.setDate(nextDate.getDate() + value) + // PostgreSQL может возвращать недели как дни (например, "7 days" вместо "1 week") + // Если количество дней кратно 7, обрабатываем как недели + if (value % 7 === 0 && value >= 7) { + const weeks = value / 7 + nextDate.setDate(nextDate.getDate() + weeks * 7) + } else { + nextDate.setDate(nextDate.getDate() + value) + } break case 'week': case 'weeks': + case 'wks': + case 'wk': nextDate.setDate(nextDate.getDate() + value * 7) break case 'month': case 'months': + case 'mons': + case 'mon': nextDate.setMonth(nextDate.getMonth() + value) break case 'year': case 'years': + case 'yrs': + case 'yr': nextDate.setFullYear(nextDate.getFullYear() + value) break default: diff --git a/play-life-web/src/components/TaskList.jsx b/play-life-web/src/components/TaskList.jsx index 41ece76..90bd125 100644 --- a/play-life-web/src/components/TaskList.jsx +++ b/play-life-web/src/components/TaskList.jsx @@ -139,26 +139,43 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) { switch (unit) { case 'minute': case 'minutes': + case 'mins': + case 'min': nextDate.setMinutes(nextDate.getMinutes() + value) break case 'hour': case 'hours': + case 'hrs': + case 'hr': nextDate.setHours(nextDate.getHours() + value) break case 'day': case 'days': - nextDate.setDate(nextDate.getDate() + value) + // PostgreSQL может возвращать недели как дни (например, "7 days" вместо "1 week") + // Если количество дней кратно 7, обрабатываем как недели + if (value % 7 === 0 && value >= 7) { + const weeks = value / 7 + nextDate.setDate(nextDate.getDate() + weeks * 7) + } else { + nextDate.setDate(nextDate.getDate() + value) + } break case 'week': case 'weeks': + case 'wks': + case 'wk': nextDate.setDate(nextDate.getDate() + value * 7) break case 'month': case 'months': + case 'mons': + case 'mon': nextDate.setMonth(nextDate.getMonth() + value) break case 'year': case 'years': + case 'yrs': + case 'yr': nextDate.setFullYear(nextDate.getFullYear() + value) break default: