Skip to content

Commit c07fc9c

Browse files
committed
feat: improve UX and fix bugs across multiple screens
1 parent 3e14f2d commit c07fc9c

17 files changed

Lines changed: 362 additions & 165 deletions

File tree

Makefile

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -91,25 +91,19 @@ lint:
9191
echo " Установите: https://golangci-lint.run/usage/install/"; \
9292
fi
9393

94+
PREFIX ?= $(HOME)/.local
95+
9496
install: build
9597
@echo "$(BLUE)Установка приложения...$(NC)"
96-
@if [ -w /usr/local/bin ]; then \
97-
cp $(BINARY_NAME) /usr/local/bin/; \
98-
echo "$(GREEN)✓ Приложение установлено в /usr/local/bin/$(BINARY_NAME)$(NC)"; \
99-
else \
100-
echo "$(RED)✗ Нет прав на запись в /usr/local/bin$(NC)"; \
101-
echo " Выполните: sudo make install"; \
102-
fi
98+
@mkdir -p $(PREFIX)/bin
99+
cp $(BINARY_NAME) $(PREFIX)/bin/
100+
@echo "$(GREEN)✓ Приложение установлено в $(PREFIX)/bin/$(BINARY_NAME)$(NC)"
101+
@echo " Убедитесь, что $$(PREFIX)/bin есть в \$$PATH"
103102

104103
uninstall:
105104
@echo "$(BLUE)Удаление приложения...$(NC)"
106-
@if [ -w /usr/local/bin ]; then \
107-
rm -f /usr/local/bin/$(BINARY_NAME); \
108-
echo "$(GREEN)✓ Приложение удалено$(NC)"; \
109-
else \
110-
echo "$(RED)✗ Нет прав на запись в /usr/local/bin$(NC)"; \
111-
echo " Выполните: sudo make uninstall"; \
112-
fi
105+
rm -f $(PREFIX)/bin/$(BINARY_NAME)
106+
@echo "$(GREEN)✓ Приложение удалено из $(PREFIX)/bin/$(BINARY_NAME)$(NC)"
113107

114108
db-clean:
115109
@echo "$(BLUE)Очистка базы данных...$(NC)"

internal/application/usecase/mood_service.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,14 @@ func (s *MoodService) GetTodayMood(ctx context.Context) (*entity.MoodEntry, erro
7070
}
7171

7272
func (s *MoodService) GetRecentMoods(ctx context.Context, limit int) ([]*entity.MoodEntry, error) {
73+
return s.GetRecentMoodsWithOffset(ctx, limit, 0)
74+
}
75+
76+
func (s *MoodService) GetRecentMoodsWithOffset(ctx context.Context, limit int, offset int) ([]*entity.MoodEntry, error) {
7377
if limit <= 0 {
7478
limit = 30
7579
}
76-
return s.repo.FindRecent(ctx, limit)
80+
return s.repo.FindRecent(ctx, limit, offset)
7781
}
7882

7983
func (s *MoodService) GetMoodsForPeriod(ctx context.Context, period Period) ([]*entity.MoodEntry, error) {
@@ -105,7 +109,10 @@ const (
105109
)
106110

107111
func (p Period) DateRange() (start, end time.Time) {
108-
now := time.Now()
112+
return p.DateRangeAt(time.Now())
113+
}
114+
115+
func (p Period) DateRangeAt(now time.Time) (start, end time.Time) {
109116
end = time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location())
110117

111118
switch p {

internal/domain/repository/mood_repository.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type MoodRepository interface {
2020

2121
FindByDateRange(ctx context.Context, start, end time.Time) ([]*entity.MoodEntry, error)
2222

23-
FindRecent(ctx context.Context, limit int) ([]*entity.MoodEntry, error)
23+
FindRecent(ctx context.Context, limit int, offset int) ([]*entity.MoodEntry, error)
2424

2525
FindAll(ctx context.Context) ([]*entity.MoodEntry, error)
2626

internal/infrastructure/i18n/constants.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,15 @@ const (
4040
RecordBoxNoteKey = "record.box_note"
4141
RecordBoxDateKey = "record.box_date"
4242
RecordWithoutNoteKey = "record.without_note"
43+
RecordPromptDateKey = "record.prompt_date"
4344

4445
StatsTitleKey = "stats.title"
4546
StatsTotalEntriesKey = "stats.total_entries"
47+
StatsTrendKey = "stats.trend"
48+
StatsTrendUpKey = "stats.trend_up"
49+
StatsTrendDownKey = "stats.trend_down"
50+
StatsTrendStableKey = "stats.trend_stable"
51+
StatsDynamicsKey = "stats.dynamics"
4652
StatsAverageKey = "stats.average"
4753
StatsMinKey = "stats.min"
4854
StatsMaxKey = "stats.max"
@@ -55,6 +61,7 @@ const (
5561
StatsQuarterKey = "stats.quarter"
5662
StatsYearKey = "stats.year"
5763
StatsAllKey = "stats.all"
64+
StatsPageKey = "stats.page"
5865
StatsTodayKey = "stats.today"
5966
StatsYesterdayKey = "stats.yesterday"
6067
StatsTomorrowKey = "stats.tomorrow"

internal/infrastructure/persistence/sqlite_mood_repository.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,15 +146,15 @@ func (r *SQLiteMoodRepository) FindByDateRange(ctx context.Context, start, end t
146146
return r.scanMoodEntries(rows)
147147
}
148148

149-
func (r *SQLiteMoodRepository) FindRecent(ctx context.Context, limit int) ([]*entity.MoodEntry, error) {
149+
func (r *SQLiteMoodRepository) FindRecent(ctx context.Context, limit int, offset int) ([]*entity.MoodEntry, error) {
150150
query := `
151151
SELECT id, date, level, note, created_at, updated_at
152152
FROM mood_entries
153153
ORDER BY date DESC
154-
LIMIT ?
154+
LIMIT ? OFFSET ?
155155
`
156156

157-
rows, err := r.db.QueryContext(ctx, query, limit)
157+
rows, err := r.db.QueryContext(ctx, query, limit, offset)
158158
if err != nil {
159159
return nil, fmt.Errorf("failed to find recent mood entries: %w", err)
160160
}
@@ -187,9 +187,9 @@ func (r *SQLiteMoodRepository) GetStatistics(ctx context.Context, start, end tim
187187
query := `
188188
SELECT
189189
COUNT(*) as total,
190-
AVG(level) as average,
191-
MIN(level) as min_level,
192-
MAX(level) as max_level
190+
COALESCE(AVG(level), 0) as average,
191+
COALESCE(MIN(level), 0) as min_level,
192+
COALESCE(MAX(level), 0) as max_level
193193
FROM mood_entries
194194
WHERE date BETWEEN ? AND ?
195195
`

internal/presentation/tui/model.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func NewModel(
4141
currentType: state.ScreenMenu,
4242
}
4343

44-
m.current = screens.NewMenuScreen(translator)
44+
m.current = screens.NewMenuScreen(ctx, translator)
4545

4646
return m
4747
}
@@ -75,6 +75,9 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
7575

7676
case state.NavigateMsg:
7777
return m, m.navigate(msg.To, msg.Params)
78+
79+
case state.NavigateBackMsg:
80+
return m, m.navigateBack()
7881
}
7982

8083
var cmd tea.Cmd
@@ -96,11 +99,12 @@ func (m *Model) navigate(to state.ScreenType, params interface{}) tea.Cmd {
9699

97100
switch to {
98101
case state.ScreenMenu:
99-
m.current = screens.NewMenuScreen(m.translator)
102+
m.current = screens.NewMenuScreen(m.ctx, m.translator)
100103

101104
case state.ScreenMoodForm:
102105
if p, ok := params.(state.MoodFormParams); ok {
103106
m.current = screens.NewMoodFormScreen(
107+
m.ctx,
104108
m.service,
105109
m.translator,
106110
p.Date,
@@ -109,19 +113,19 @@ func (m *Model) navigate(to state.ScreenType, params interface{}) tea.Cmd {
109113
}
110114

111115
case state.ScreenCalendar:
112-
m.current = screens.NewCalendarScreen(m.service, m.translator)
116+
m.current = screens.NewCalendarScreen(m.ctx, m.service, m.translator)
113117

114118
case state.ScreenHistory:
115-
m.current = screens.NewHistoryScreen(m.service, m.translator)
119+
m.current = screens.NewHistoryScreen(m.ctx, m.service, m.translator)
116120

117121
case state.ScreenStats:
118-
m.current = screens.NewStatsScreen(m.service, m.translator)
122+
m.current = screens.NewStatsScreen(m.ctx, m.service, m.translator)
119123

120124
case state.ScreenSettings:
121-
m.current = screens.NewSettingsScreen(m.translator)
125+
m.current = screens.NewSettingsScreen(m.ctx, m.translator)
122126

123127
case state.ScreenLanguageSettings:
124-
m.current = screens.NewLanguageSettingsScreen(m.translator, m.settingsRepo)
128+
m.current = screens.NewLanguageSettingsScreen(m.ctx, m.translator, m.settingsRepo)
125129
}
126130

127131
return m.current.Init()

internal/presentation/tui/screens/calendar_screen.go

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,31 @@ import (
1313
"github.com/ignavan39/mood-diary/internal/domain/entity"
1414
"github.com/ignavan39/mood-diary/internal/infrastructure/i18n"
1515
"github.com/ignavan39/mood-diary/internal/presentation/styles"
16+
"github.com/ignavan39/mood-diary/internal/presentation/tui/components"
1617
"github.com/ignavan39/mood-diary/internal/presentation/tui/formatters"
1718
"github.com/ignavan39/mood-diary/internal/presentation/tui/state"
1819
)
1920

2021
type CalendarScreen struct {
2122
state.BaseState
2223

24+
ctx context.Context
2325
service *usecase.MoodService
2426
translator i18n.Translator
2527

2628
currentMonth time.Time
2729
selectedDate time.Time
2830
moodData map[time.Time]*entity.MoodEntry
2931

30-
cursorRow int
31-
cursorCol int
32+
cursorRow int
33+
cursorCol int
34+
confirmDlg *components.ConfirmationDialog
3235
}
3336

34-
func NewCalendarScreen(service *usecase.MoodService, translator i18n.Translator) *CalendarScreen {
37+
func NewCalendarScreen(ctx context.Context, service *usecase.MoodService, translator i18n.Translator) *CalendarScreen {
3538
now := time.Now()
3639
return &CalendarScreen{
40+
ctx: ctx,
3741
service: service,
3842
translator: translator,
3943
currentMonth: time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC),
@@ -55,6 +59,14 @@ func (s *CalendarScreen) Init() tea.Cmd {
5559
}
5660

5761
func (s *CalendarScreen) Update(msg tea.Msg) (state.Screen, tea.Cmd) {
62+
if s.confirmDlg != nil && !s.confirmDlg.IsDone() {
63+
cmd := s.confirmDlg.Update(msg)
64+
if s.confirmDlg.IsDone() {
65+
s.confirmDlg = nil
66+
}
67+
return s, cmd
68+
}
69+
5870
switch msg := msg.(type) {
5971
case tea.WindowSizeMsg:
6072
s.SetSize(msg.Width, msg.Height)
@@ -106,11 +118,13 @@ func (s *CalendarScreen) handleKeyMsg(msg tea.KeyMsg) (state.Screen, tea.Cmd) {
106118

107119
entry := s.moodData[s.selectedDate]
108120
if entry != nil {
109-
return s, s.deleteMood(entry)
121+
msg := fmt.Sprintf(s.t(i18n.EditDeleteWarningKey), formatters.FormatDate(entry.Date))
122+
s.confirmDlg = components.NewConfirmation(msg, s.translator, s.deleteMood(entry), nil)
123+
return s, nil
110124
}
111125

112126
case "esc", "q":
113-
return s, state.NavigateToMenu()
127+
return s, state.NavigateBack()
114128
}
115129

116130
return s, nil
@@ -163,16 +177,17 @@ func (s *CalendarScreen) updateCursorPosition() {
163177

164178
func (s *CalendarScreen) loadMonthData() tea.Cmd {
165179
return func() tea.Msg {
166-
ctx := context.Background()
167-
entries, err := s.service.GetAllMoods(ctx)
180+
start := time.Date(s.currentMonth.Year(), s.currentMonth.Month(), 1, 0, 0, 0, 0, time.UTC)
181+
end := start.AddDate(0, 1, -1)
182+
end = time.Date(end.Year(), end.Month(), end.Day(), 23, 59, 59, 0, time.UTC)
183+
184+
entries, err := s.service.GetMoodsByDateRange(s.ctx, start, end)
168185

169186
data := make(map[time.Time]*entity.MoodEntry)
170187
if err == nil {
171188
for _, e := range entries {
172189
day := time.Date(e.Date.Year(), e.Date.Month(), e.Date.Day(), 0, 0, 0, 0, time.UTC)
173-
if day.Year() == s.currentMonth.Year() && day.Month() == s.currentMonth.Month() {
174-
data[day] = e
175-
}
190+
data[day] = e
176191
}
177192
} else {
178193
return state.ErrorMsg{Error: err}
@@ -184,8 +199,7 @@ func (s *CalendarScreen) loadMonthData() tea.Cmd {
184199

185200
func (s *CalendarScreen) deleteMood(entry *entity.MoodEntry) tea.Cmd {
186201
return func() tea.Msg {
187-
ctx := context.Background()
188-
err := s.service.DeleteMood(ctx, entry.Date)
202+
err := s.service.DeleteMood(s.ctx, entry.Date)
189203
if err != nil {
190204
return state.ErrorMsg{Error: err}
191205
}
@@ -201,6 +215,11 @@ func (s *CalendarScreen) View() string {
201215
b.WriteString(header)
202216
b.WriteString("\n\n")
203217

218+
if s.confirmDlg != nil {
219+
b.WriteString(s.confirmDlg.View())
220+
return lipgloss.NewStyle().Padding(2, 4).Render(b.String())
221+
}
222+
204223
if s.Error != nil {
205224
b.WriteString(styles.ErrorStyle.Render(s.t(i18n.CommonErrorPrefixKey) + s.Error.Error()))
206225
b.WriteString("\n\n")

0 commit comments

Comments
 (0)