timet 1.5.2 → 1.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -0
- data/CHANGELOG.md +37 -0
- data/lib/timet/application.rb +7 -7
- data/lib/timet/application_helper.rb +2 -2
- data/lib/timet/database.rb +1 -3
- data/lib/timet/database_sync_helper.rb +4 -206
- data/lib/timet/database_syncer.rb +214 -0
- data/lib/timet/s3_supabase.rb +12 -5
- data/lib/timet/table.rb +25 -9
- data/lib/timet/tag_distribution.rb +12 -1
- data/lib/timet/time_block_chart.rb +145 -121
- data/lib/timet/time_report.rb +50 -25
- data/lib/timet/time_statistics.rb +0 -2
- data/lib/timet/validation_edit_helper.rb +8 -6
- data/lib/timet/version.rb +2 -2
- metadata +5 -4
data/lib/timet/table.rb
CHANGED
@@ -1,9 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# require_relative 'color_codes'
|
4
|
+
require 'timet/time_report_helper'
|
3
5
|
module Timet
|
4
|
-
# This
|
6
|
+
# This class is responsible for formatting the output of the `timet` application.
|
5
7
|
# It provides methods for formatting the table header, separators, and rows.
|
6
|
-
|
8
|
+
class Table
|
9
|
+
include TimeReportHelper
|
10
|
+
|
11
|
+
attr_reader :filter, :items
|
12
|
+
|
13
|
+
def initialize(filter, items, db)
|
14
|
+
@filter = filter
|
15
|
+
@items = items
|
16
|
+
@db = db
|
17
|
+
end
|
18
|
+
|
7
19
|
# Generates and displays a table summarizing time entries, including headers, time blocks, and total durations.
|
8
20
|
#
|
9
21
|
# @example
|
@@ -64,22 +76,26 @@ module Timet
|
|
64
76
|
|
65
77
|
# Processes time entries and generates a time block structure.
|
66
78
|
#
|
67
|
-
#
|
79
|
+
# This method iterates over each item in the `items` array, displays the time entry (if enabled),
|
80
|
+
# and processes the time block item to build a nested hash representing the time block structure.
|
81
|
+
#
|
82
|
+
# @param display [Boolean] Whether to display the time entry during processing. Defaults to `true`.
|
83
|
+
# @return [Hash] A nested hash representing the time block structure, where keys are dates and values
|
84
|
+
# are processed time block items.
|
68
85
|
#
|
69
86
|
# @note
|
70
|
-
# - The method
|
71
|
-
# -
|
72
|
-
# - It then processes the time block item using `process_time_block_item`.
|
87
|
+
# - The method uses `display_time_entry` to display the time entry if `display` is `true`.
|
88
|
+
# - It processes each time block item using `process_time_block_item`.
|
73
89
|
# - The `TimeHelper.extract_date` method is used to extract the date from the items.
|
74
90
|
#
|
75
91
|
# @see #display_time_entry
|
76
92
|
# @see #process_time_block_item
|
77
93
|
# @see TimeHelper#extract_date
|
78
|
-
def process_time_entries
|
94
|
+
def process_time_entries(display: true)
|
79
95
|
time_block = Hash.new { |hash, key| hash[key] = {} }
|
80
96
|
|
81
|
-
items.each_with_index do |item, idx|
|
82
|
-
display_time_entry(item, TimeHelper.extract_date(items, idx))
|
97
|
+
@items.each_with_index do |item, idx|
|
98
|
+
display_time_entry(item, TimeHelper.extract_date(@items, idx)) if display
|
83
99
|
time_block = process_time_block_item(item, time_block)
|
84
100
|
end
|
85
101
|
|
@@ -41,6 +41,17 @@ module Timet
|
|
41
41
|
def process_and_print_tags(time_stats, total, colors)
|
42
42
|
print_summary(time_stats, total)
|
43
43
|
print_tags_info(time_stats, total, colors)
|
44
|
+
print_footer
|
45
|
+
end
|
46
|
+
|
47
|
+
# Prints the footer information.
|
48
|
+
#
|
49
|
+
# @return [void] This method outputs the footer information to the standard output.
|
50
|
+
def print_footer
|
51
|
+
puts '-' * 45
|
52
|
+
puts 'T:'.rjust(4).red + 'The total duration'.gray
|
53
|
+
puts 'AVG:'.rjust(4).red + 'The average duration'.gray
|
54
|
+
puts 'SD:'.rjust(4).red + 'The standard deviation of the durations'.gray
|
44
55
|
end
|
45
56
|
|
46
57
|
# Prints the summary information including total duration, average duration, and standard deviation.
|
@@ -104,7 +115,7 @@ module Timet
|
|
104
115
|
# calculate_value_and_bar_length(50, 100, 2) #=> [50.0, 25]
|
105
116
|
def calculate_value_and_bar_length(duration, total)
|
106
117
|
value = duration.to_f / total
|
107
|
-
percentage_value = (
|
118
|
+
percentage_value = (value * 100).round(1)
|
108
119
|
bar_length = (value * MAX_BAR_LENGTH).round
|
109
120
|
[percentage_value, bar_length]
|
110
121
|
end
|
@@ -1,9 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Timet
|
4
|
-
#
|
5
|
-
#
|
6
|
-
|
4
|
+
#
|
5
|
+
# The TimeBlockChart class is responsible for generating and printing a visual representation
|
6
|
+
# of time blocks for a given set of data. It uses character mapping to represent different
|
7
|
+
# time ranges and provides methods to print the chart with headers, footers, and colored blocks.
|
8
|
+
#
|
9
|
+
# Example usage:
|
10
|
+
# time_block = {
|
11
|
+
# "2023-10-01" => { "08" => [3600, "work"], "09" => [1800, "break"] },
|
12
|
+
# "2023-10-02" => { "10" => [4500, "work"] }
|
13
|
+
# }
|
14
|
+
# colors = { "work" => 31, "break" => 32 }
|
15
|
+
# chart = TimeBlockChart.new(table)
|
16
|
+
# chart.print_time_block_chart(table, colors)
|
17
|
+
#
|
18
|
+
# @attr_reader [Integer] start_hour The starting hour of the time block
|
19
|
+
# @attr_reader [Integer] end_hour The ending hour of the time block
|
20
|
+
class TimeBlockChart
|
21
|
+
# Character mapping for different time ranges
|
7
22
|
CHAR_MAPPING = {
|
8
23
|
0..120 => '_',
|
9
24
|
121..450 => '▁',
|
@@ -16,87 +31,115 @@ module Timet
|
|
16
31
|
3151..3600 => '█'
|
17
32
|
}.freeze
|
18
33
|
|
34
|
+
# Separator character for the chart
|
19
35
|
SEPARATOR_CHAR = '░'
|
20
36
|
|
21
|
-
#
|
37
|
+
# Initializes a new TimeBlockChart instance.
|
22
38
|
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
# Example: { "block1" => { 10 => "value1", 11 => "value2" }, "block2" => { 12 => "value3" } }
|
26
|
-
# @param colors [Hash] A hash where the keys are time slots and the values are the colors to be used
|
27
|
-
# for those slots.
|
28
|
-
# Example: { 10 => "red", 11 => "blue", 12 => "green" }
|
39
|
+
# This method sets up the time block chart by processing the time entries from the provided table
|
40
|
+
# and determining the start and end hours for the chart based on the time block data.
|
29
41
|
#
|
30
|
-
# @
|
42
|
+
# @param table [Table] The table instance containing the time entries to be processed.
|
43
|
+
# @return [void] This method does not return a value; it initializes the instance variables.
|
31
44
|
#
|
32
|
-
# @
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
45
|
+
# @note
|
46
|
+
# - The `@time_block` instance variable is populated by processing the time entries from the table.
|
47
|
+
# - The `@start_hour` and `@end_hour` instance variables are calculated based on the earliest and latest
|
48
|
+
# hours present in the time block data.
|
36
49
|
#
|
37
|
-
# @
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
def print_time_block_chart(time_block, colors)
|
43
|
-
start_hour = time_block.values.map(&:keys).flatten.uniq.min.to_i
|
44
|
-
print_header(start_hour)
|
45
|
-
print_blocks(time_block, colors, start_hour)
|
50
|
+
# @see Table#process_time_entries
|
51
|
+
def initialize(table)
|
52
|
+
@time_block = table.process_time_entries(display: false)
|
53
|
+
@start_hour = @time_block.values.map(&:keys).flatten.uniq.min.to_i
|
54
|
+
@end_hour = @time_block.values.map(&:keys).flatten.uniq.max.to_i
|
46
55
|
end
|
47
56
|
|
48
|
-
# Prints the
|
57
|
+
# Prints the time block chart.
|
49
58
|
#
|
50
|
-
#
|
51
|
-
#
|
59
|
+
# This method formats and prints the time block chart, including the header and the time blocks
|
60
|
+
# for each entry. The chart is color-coded based on the provided color mapping for different tags.
|
52
61
|
#
|
53
|
-
# @param
|
62
|
+
# @param table [Hash] The time block data to be displayed in the chart.
|
63
|
+
# @param colors [Hash] A mapping of tags to colors, used to color-code the time blocks.
|
64
|
+
# @return [void] This method does not return a value; it performs side effects such as printing the chart.
|
54
65
|
#
|
55
|
-
# @
|
66
|
+
# @example Print a time block chart
|
67
|
+
# chart = TimeBlockChart.new(table)
|
68
|
+
# chart.print_time_block_chart(table, colors)
|
56
69
|
#
|
57
|
-
# @
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
70
|
+
# @note
|
71
|
+
# - The method first prints the header of the chart, which includes the time range.
|
72
|
+
# - It then prints the time blocks, using the provided color mapping to visually distinguish
|
73
|
+
# between different tags.
|
74
|
+
#
|
75
|
+
# @see #print_header
|
76
|
+
# @see #print_blocks
|
77
|
+
def print_time_block_chart(table, colors)
|
78
|
+
print_header
|
79
|
+
print_blocks(table, colors)
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
# Prints the header of the chart
|
62
85
|
#
|
63
|
-
# @
|
64
|
-
|
65
|
-
def print_header(start_hour)
|
86
|
+
# @return [void]
|
87
|
+
def print_header
|
66
88
|
puts
|
67
89
|
print ' ' * 19
|
68
|
-
(start_hour
|
90
|
+
(@start_hour..@end_hour + 1).each { |hour| print format('%02d', hour).rjust(4) }
|
69
91
|
puts
|
70
|
-
puts '┌╴W ╴╴╴╴╴╴⏰╴╴╴╴╴╴┼'.gray + "#{'╴' * (
|
92
|
+
puts '┌╴W ╴╴╴╴╴╴⏰╴╴╴╴╴╴┼'.gray + "#{'╴' * (@end_hour - @start_hour + 1) * 4}╴╴╴┼".gray
|
71
93
|
end
|
72
94
|
|
73
|
-
# Prints the time blocks for each date in the
|
95
|
+
# Prints the time blocks for each date in the time block data.
|
74
96
|
#
|
75
|
-
#
|
76
|
-
#
|
77
|
-
#
|
78
|
-
#
|
79
|
-
|
80
|
-
|
97
|
+
# This method iterates over the time block data, formats and prints the date information,
|
98
|
+
# prints the time blocks for each date using the provided color mapping, and calculates
|
99
|
+
# and prints the total hours for each day. It also prints a footer at the end.
|
100
|
+
#
|
101
|
+
# @param table [Hash] The time block data containing the time entries for each date.
|
102
|
+
# @param colors [Hash] A mapping of tags to colors, used to color-code the time blocks.
|
103
|
+
# @return [void] This method does not return a value; it performs side effects such as printing
|
104
|
+
# the time blocks and related information.
|
105
|
+
#
|
106
|
+
# @example Print time blocks
|
107
|
+
# chart = TimeBlockChart.new(table)
|
108
|
+
# chart.print_blocks(table, colors)
|
109
|
+
#
|
110
|
+
# @note
|
111
|
+
# - The method skips processing if the `table` parameter is `nil`.
|
112
|
+
# - For each date in the time block data, it formats and prints the date and day of the week.
|
113
|
+
# - It prints the time blocks using the provided color mapping to visually distinguish
|
114
|
+
# between different tags.
|
115
|
+
# - It calculates and prints the total hours for each day.
|
116
|
+
# - A footer is printed at the end to provide a visual separation.
|
117
|
+
#
|
118
|
+
# @see #format_and_print_date_info
|
119
|
+
# @see #print_time_blocks
|
120
|
+
# @see #calculate_and_print_hours
|
121
|
+
# @see #print_footer
|
122
|
+
def print_blocks(table, colors)
|
123
|
+
return unless table
|
81
124
|
|
82
125
|
weeks = []
|
83
|
-
time_block.each_key do |date_string|
|
126
|
+
@time_block.each_key do |date_string|
|
84
127
|
date = Date.parse(date_string)
|
85
|
-
day = date.strftime('%a')[0..
|
128
|
+
day = date.strftime('%a')[0..2]
|
86
129
|
|
87
|
-
format_and_print_date_info(date_string, day, weeks
|
130
|
+
format_and_print_date_info(date_string, day, weeks)
|
88
131
|
|
89
|
-
time_block_initial = time_block[date_string]
|
90
|
-
print_time_blocks(
|
132
|
+
time_block_initial = @time_block[date_string]
|
133
|
+
print_time_blocks(time_block_initial, colors)
|
91
134
|
|
92
135
|
calculate_and_print_hours(time_block_initial)
|
93
136
|
end
|
94
|
-
print_footer
|
137
|
+
print_footer
|
95
138
|
end
|
96
139
|
|
97
|
-
# Calculates the total hours
|
140
|
+
# Calculates and prints the total hours for a day
|
98
141
|
#
|
99
|
-
# @param
|
142
|
+
# @param [Hash] time_block_initial The initial time block data for a day
|
100
143
|
# @return [void]
|
101
144
|
def calculate_and_print_hours(time_block_initial)
|
102
145
|
total_seconds = time_block_initial.values.map { |item| item[0] }.sum
|
@@ -105,104 +148,88 @@ module Timet
|
|
105
148
|
puts
|
106
149
|
end
|
107
150
|
|
108
|
-
# Formats and prints the date information
|
151
|
+
# Formats and prints the date information
|
109
152
|
#
|
110
|
-
# @param
|
111
|
-
# @param
|
112
|
-
# @param
|
113
|
-
# @param start_hour [Integer] The starting hour for the time blocks.
|
153
|
+
# @param [String] date_string The date string
|
154
|
+
# @param [String] day The day of the week
|
155
|
+
# @param [Array] weeks The list of weeks
|
114
156
|
# @return [void]
|
115
|
-
def format_and_print_date_info(date_string, day, weeks
|
157
|
+
def format_and_print_date_info(date_string, day, weeks)
|
116
158
|
weekend = date_string
|
117
159
|
day = day.red if %w[Sa Su].include?(day)
|
118
160
|
weekend = weekend.red if %w[Sa Su].include?(day)
|
119
161
|
|
120
|
-
week = format_and_print_week(date_string, weeks
|
162
|
+
week = format_and_print_week(date_string, weeks)
|
121
163
|
|
122
|
-
print '┆'.gray + "#{week} #{weekend} #{day}
|
164
|
+
print '┆'.gray + "#{week} #{weekend} #{day}" + '┆- '.gray
|
123
165
|
end
|
124
166
|
|
125
|
-
# Formats and prints the week information
|
167
|
+
# Formats and prints the week information
|
126
168
|
#
|
127
|
-
# @param
|
128
|
-
# @param
|
129
|
-
# @
|
130
|
-
|
131
|
-
def format_and_print_week(date_string, weeks, start_hour)
|
169
|
+
# @param [String] date_string The date string
|
170
|
+
# @param [Array] weeks The list of weeks
|
171
|
+
# @return [String] The formatted week string
|
172
|
+
def format_and_print_week(date_string, weeks)
|
132
173
|
week, current_index = determine_week(date_string, weeks)
|
133
|
-
print_separator(
|
174
|
+
print_separator(week, current_index)
|
134
175
|
week
|
135
176
|
end
|
136
177
|
|
137
|
-
# Determines the week
|
178
|
+
# Determines the week for a given date
|
138
179
|
#
|
139
|
-
# @param
|
140
|
-
# @param
|
141
|
-
# @return [Array
|
180
|
+
# @param [String] date_string The date string
|
181
|
+
# @param [Array] weeks The list of weeks
|
182
|
+
# @return [Array] The week string and current index
|
142
183
|
def determine_week(date_string, weeks)
|
143
184
|
weeks << Date.parse(date_string).cweek
|
144
185
|
current_index = weeks.size - 1
|
145
186
|
current_week = weeks[current_index]
|
146
|
-
week = current_week == weeks[current_index - 1] && current_index.positive?
|
187
|
+
week = if current_week == weeks[current_index - 1] && current_index.positive?
|
188
|
+
' '
|
189
|
+
else
|
190
|
+
format('%02d', current_week).to_s.underline
|
191
|
+
end
|
147
192
|
[week, current_index]
|
148
193
|
end
|
149
194
|
|
150
|
-
# Prints the separator line
|
195
|
+
# Prints the separator line
|
151
196
|
#
|
152
|
-
# @param
|
153
|
-
# @param
|
154
|
-
# @param current_index [Integer] The current index in the weeks array.
|
197
|
+
# @param [String] week The week string
|
198
|
+
# @param [Integer] current_index The current index
|
155
199
|
# @return [void]
|
156
|
-
def print_separator(
|
200
|
+
def print_separator(week, current_index)
|
157
201
|
return unless week != ' ' && current_index.positive?
|
158
202
|
|
159
203
|
sep = SEPARATOR_CHAR
|
160
|
-
puts "┆#{sep * 17}┼#{sep * (
|
204
|
+
puts "┆#{sep * 17}┼#{sep * (@end_hour - @start_hour + 1) * 4}#{sep * 3}┼#{sep * 4}".gray
|
161
205
|
end
|
162
206
|
|
163
|
-
# Prints the footer of the
|
207
|
+
# Prints the footer of the chart
|
164
208
|
#
|
165
|
-
# @
|
166
|
-
|
167
|
-
def print_footer(start_hour)
|
209
|
+
# @return [void]
|
210
|
+
def print_footer
|
168
211
|
timet = "\e]8;;https://github.com/frankvielma/timet/\aTimet\e]8;;\a".green
|
169
|
-
puts '└╴╴╴╴╴╴╴'.gray + timet + "╴╴╴╴╴┴#{'╴' * (
|
212
|
+
puts '└╴╴╴╴╴╴╴'.gray + timet + "╴╴╴╴╴┴#{'╴' * (@end_hour - @start_hour + 1) * 4}╴╴╴┴".gray
|
170
213
|
puts
|
171
214
|
end
|
172
215
|
|
173
|
-
# Prints time blocks for
|
216
|
+
# Prints the time blocks for a given day
|
174
217
|
#
|
175
|
-
# @param
|
176
|
-
# @param
|
177
|
-
# are arrays containing block data.
|
178
|
-
# @param colors [Hash] A hash mapping tags to color codes.
|
218
|
+
# @param [Hash] time_block_initial The initial time block data for a day
|
219
|
+
# @param [Hash] colors The color mapping for different tags
|
179
220
|
# @return [void]
|
180
|
-
|
181
|
-
|
182
|
-
# time_block_initial = {
|
183
|
-
# '01' => ['block_char_data', 'tag']
|
184
|
-
# }
|
185
|
-
# colors = { 'tag' => 1 }
|
186
|
-
# print_time_blocks(1, time_block_initial, colors) # Prints time blocks for hours 1 to 23
|
187
|
-
def print_time_blocks(start_time, time_block_initial, colors)
|
188
|
-
(start_time..23).each do |hour|
|
221
|
+
def print_time_blocks(time_block_initial, colors)
|
222
|
+
(@start_hour..@end_hour).each do |hour|
|
189
223
|
tag, block_char = get_formatted_block_char(hour, time_block_initial)
|
190
224
|
print_colored_block(block_char, tag, colors)
|
191
225
|
end
|
192
226
|
end
|
193
227
|
|
194
|
-
#
|
195
|
-
#
|
196
|
-
# @param hour [Integer] The hour for which to retrieve the block character.
|
197
|
-
# @param time_block_initial [Hash] A hash containing time block data, where keys are formatted hours and values
|
198
|
-
# are arrays containing block data.
|
199
|
-
# @return [Array<(String, String)>] An array containing the tag and the block character.
|
228
|
+
# Gets the formatted block character for a given hour
|
200
229
|
#
|
201
|
-
# @
|
202
|
-
#
|
203
|
-
#
|
204
|
-
# }
|
205
|
-
# get_formatted_block_char(1, time_block_initial) #=> ['tag', 'block_char']
|
230
|
+
# @param [Integer] hour The hour
|
231
|
+
# @param [Hash] time_block_initial The initial time block data for a day
|
232
|
+
# @return [Array] The tag and block character
|
206
233
|
def get_formatted_block_char(hour, time_block_initial)
|
207
234
|
formatted_hour = format('%02d', hour)
|
208
235
|
hour_data = time_block_initial[formatted_hour]
|
@@ -210,15 +237,12 @@ module Timet
|
|
210
237
|
[tag, get_block_char(hour_data&.first)]
|
211
238
|
end
|
212
239
|
|
213
|
-
# Prints
|
240
|
+
# Prints the colored block character
|
214
241
|
#
|
215
|
-
# @param
|
216
|
-
# @param
|
217
|
-
# @param
|
218
|
-
#
|
219
|
-
# @example
|
220
|
-
# colors = { 'tag' => 1 }
|
221
|
-
# print_colored_block('X', 'tag', colors) # Prints a colored block character 'XX'
|
242
|
+
# @param [String] block_char The block character
|
243
|
+
# @param [String] tag The tag
|
244
|
+
# @param [Hash] colors The color mapping for different tags
|
245
|
+
# @return [void]
|
222
246
|
def print_colored_block(block_char, tag, colors)
|
223
247
|
color_code = colors[tag]
|
224
248
|
block = block_char * 2
|
@@ -226,10 +250,10 @@ module Timet
|
|
226
250
|
print colored_block.rjust(4)
|
227
251
|
end
|
228
252
|
|
229
|
-
#
|
253
|
+
# Gets the block character for a given value
|
230
254
|
#
|
231
|
-
# @param
|
232
|
-
# @return [String] The block character
|
255
|
+
# @param [Integer, nil] value The value
|
256
|
+
# @return [String] The block character
|
233
257
|
def get_block_char(value)
|
234
258
|
return ' ' unless value
|
235
259
|
|
data/lib/timet/time_report.rb
CHANGED
@@ -14,8 +14,6 @@ module Timet
|
|
14
14
|
# a formatted table with the relevant information.
|
15
15
|
class TimeReport
|
16
16
|
include TimeReportHelper
|
17
|
-
include Table
|
18
|
-
include TimeBlockChart
|
19
17
|
include TagDistribution
|
20
18
|
|
21
19
|
# Provides access to the database instance.
|
@@ -33,37 +31,55 @@ module Timet
|
|
33
31
|
# Initializes a new instance of the TimeReport class.
|
34
32
|
#
|
35
33
|
# @param db [Database] The database instance to use for fetching data.
|
36
|
-
# @param options [Hash] A hash containing optional parameters.
|
37
|
-
# @option options [String, nil] :filter The filter to apply when fetching items. Possible values include
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
# @
|
44
|
-
#
|
34
|
+
# @param options [Hash] A hash containing optional parameters for configuring the report.
|
35
|
+
# @option options [String, nil] :filter The filter to apply when fetching items. Possible values include:
|
36
|
+
# - 'today': Filters items for the current day.
|
37
|
+
# - 'yesterday': Filters items for the previous day.
|
38
|
+
# - 'week': Filters items for the current week.
|
39
|
+
# - 'month': Filters items for the current month.
|
40
|
+
# - A date range in the format 'YYYY-MM-DD..YYYY-MM-DD': Filters items within the specified date range.
|
41
|
+
# @option options [String, nil] :tag The tag to filter the items by. Only items with this tag will be included.
|
42
|
+
# @option options [String, nil] :csv The filename to use when exporting the report to CSV. If provided, the report
|
43
|
+
# will be exported to the specified file.
|
44
|
+
# @option options [String, nil] :ics The filename to use when exporting the report to iCalendar format. If provided,
|
45
|
+
# the report will be exported to the specified file.
|
46
|
+
#
|
47
|
+
# @return [void] This method does not return a value; it initializes the instance variables and prepares the report.
|
45
48
|
#
|
46
49
|
# @example Initialize a new TimeReport instance with a filter and tag
|
47
50
|
# TimeReport.new(db, filter: 'today', tag: 'work', csv: 'report.csv', ics: 'icalendar.ics')
|
51
|
+
#
|
52
|
+
# @example Initialize a new TimeReport instance with a date range filter
|
53
|
+
# TimeReport.new(db, filter: '2023-10-01..2023-10-31', tag: 'project')
|
54
|
+
#
|
55
|
+
# @note
|
56
|
+
# - If no filter is provided, all items from the database will be fetched.
|
57
|
+
# - The `@table` instance variable is initialized with the filtered items and filter configuration.
|
48
58
|
def initialize(db, options = {})
|
49
59
|
@db = db
|
50
60
|
@csv_filename = options[:csv]
|
51
61
|
@ics_filename = options[:ics]
|
52
62
|
@filter = formatted_filter(options[:filter])
|
53
63
|
@items = options[:filter] ? filter_items(@filter, options[:tag]) : @db.all_items
|
64
|
+
@table = Table.new(@filter, @items, @db)
|
54
65
|
end
|
55
66
|
|
56
67
|
# Displays the report of tracked time entries.
|
57
68
|
#
|
69
|
+
# This method formats and prints the report, including the table header, rows, total duration,
|
70
|
+
# a time block chart, and tag distribution. If no tracked time entries are found for the specified filter,
|
71
|
+
# it displays a message indicating no data is available.
|
72
|
+
#
|
58
73
|
# @return [void] This method does not return a value; it performs side effects such as printing the report.
|
59
74
|
#
|
60
75
|
# @example Display the report
|
61
76
|
# time_report.display
|
62
77
|
#
|
63
|
-
# @note
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
78
|
+
# @note
|
79
|
+
# - The method checks if there are any tracked time entries. If not, it prints a message and exits.
|
80
|
+
# - It uses the `@table` instance to format and display the table.
|
81
|
+
# - A time block chart is generated and printed using the `TimeBlockChart` class.
|
82
|
+
# - The tag distribution is calculated and displayed based on the unique colors assigned to tags.
|
67
83
|
#
|
68
84
|
# @see #table
|
69
85
|
# @see #print_time_block_chart
|
@@ -71,29 +87,38 @@ module Timet
|
|
71
87
|
def display
|
72
88
|
return puts 'No tracked time found for the specified filter.' if @items.empty?
|
73
89
|
|
74
|
-
|
75
|
-
|
90
|
+
@table.table
|
76
91
|
colors = @items.map { |x| x[3] }.uniq.each_with_index.to_h
|
77
|
-
|
78
|
-
|
92
|
+
chart = TimeBlockChart.new(@table)
|
93
|
+
chart.print_time_block_chart(@table, colors)
|
79
94
|
tag_distribution(colors)
|
80
95
|
end
|
81
96
|
|
82
97
|
# Displays a single row of the report.
|
83
98
|
#
|
84
|
-
#
|
99
|
+
# This method formats and prints a single row of the report, including the table header, the specified row,
|
100
|
+
# a separator, and the total duration. It is used to display individual time entries in a structured format.
|
101
|
+
#
|
102
|
+
# @param item [Array] The item (time entry) to display. The item is expected to contain the necessary data
|
103
|
+
# for the row, such as the time, description, and duration.
|
85
104
|
#
|
86
105
|
# @return [void] This method does not return a value; it performs side effects such as printing the row.
|
87
106
|
#
|
88
107
|
# @example Display a single row
|
89
108
|
# time_report.show_row(item)
|
90
109
|
#
|
91
|
-
# @note
|
110
|
+
# @note
|
111
|
+
# - The method uses the `@table` instance to format and display the table header and row.
|
112
|
+
# - A separator is printed after the row to visually distinguish it from other rows.
|
113
|
+
# - The total duration is displayed at the end of the row.
|
114
|
+
#
|
115
|
+
# @see #table
|
116
|
+
# @see #display_time_entry
|
92
117
|
def show_row(item)
|
93
|
-
header
|
94
|
-
display_time_entry(item)
|
95
|
-
puts separator
|
96
|
-
total
|
118
|
+
@table.header
|
119
|
+
@table.display_time_entry(item)
|
120
|
+
puts @table.separator
|
121
|
+
@table.total
|
97
122
|
end
|
98
123
|
|
99
124
|
private
|
@@ -34,8 +34,6 @@ module Timet
|
|
34
34
|
# - :avg [Numeric] The average duration.
|
35
35
|
# - :sd [Numeric] The standard deviation of the durations.
|
36
36
|
def totals
|
37
|
-
@duration_by_tag.values.flatten
|
38
|
-
|
39
37
|
durations = @duration_by_tag.values.flatten
|
40
38
|
{ total: @total_duration, avg: durations.mean, sd: durations.standard_deviation }
|
41
39
|
end
|
@@ -51,7 +51,8 @@ module Timet
|
|
51
51
|
# @note The method formats the date value and checks if it is valid.
|
52
52
|
# @note If the date value is valid, it updates the time field with the new value.
|
53
53
|
# @note If the date value is invalid, it prints an error message.
|
54
|
-
def process_and_update_time_field(
|
54
|
+
def process_and_update_time_field(*args)
|
55
|
+
item, field, date_value, id = args
|
55
56
|
formatted_date = TimeHelper.format_time_string(date_value)
|
56
57
|
|
57
58
|
return print_error(date_value) unless formatted_date
|
@@ -82,17 +83,17 @@ module Timet
|
|
82
83
|
#
|
83
84
|
# @param item [Array] The tracking item to be updated.
|
84
85
|
# @param field [String] The time field to be updated.
|
85
|
-
# @param
|
86
|
+
# @param new_time [String] The new time value.
|
86
87
|
#
|
87
88
|
# @return [Time] The updated time value.
|
88
89
|
#
|
89
90
|
# @example Update the 'start' field of a tracking item with a formatted date value
|
90
|
-
# update_time_field(item, 'start', '
|
91
|
-
def update_time_field(item, field,
|
91
|
+
# update_time_field(item, 'start', '11:10:00')
|
92
|
+
def update_time_field(item, field, new_time)
|
92
93
|
field_index = Timet::Application::FIELD_INDEX[field]
|
93
94
|
timestamp = item[field_index]
|
94
95
|
current_time = Time.at(timestamp || TimeHelper.current_timestamp).to_s.split
|
95
|
-
current_time[1] =
|
96
|
+
current_time[1] = new_time
|
96
97
|
DateTime.strptime(current_time.join(' '), '%Y-%m-%d %H:%M:%S %z').to_time
|
97
98
|
end
|
98
99
|
|
@@ -107,7 +108,8 @@ module Timet
|
|
107
108
|
#
|
108
109
|
# @example Validate a new 'start' time value
|
109
110
|
# valid_time_value?(item, 'start', 1633072800, 1)
|
110
|
-
def valid_time_value?(
|
111
|
+
def valid_time_value?(*args)
|
112
|
+
item, field, new_value_epoch, id = args
|
111
113
|
item_start = fetch_item_start(item)
|
112
114
|
item_end = fetch_item_end(item)
|
113
115
|
item_before_end = fetch_item_before_end(id, item_start)
|