timet 1.3.2 → 1.4.1

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.
@@ -0,0 +1,300 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Timet
4
+ # This module is responsible for formatting the output of the `timet` application.
5
+ # It provides methods for formatting the table header, separators, and rows.
6
+ module Table
7
+ # Generates and displays a table summarizing time entries, including headers, time blocks, and total durations.
8
+ #
9
+ # @example
10
+ # table
11
+ #
12
+ # @return [Array<(String, Hash)>] An array containing the time block string and a hash of durations by tag.
13
+ #
14
+ # @note
15
+ # - The method relies on the `header`, `process_time_entries`, `separator`, and `total` methods.
16
+ # - The `header` method is responsible for printing the table header.
17
+ # - The `process_time_entries` method processes the time entries and returns the time block and duration by tag.
18
+ # - The `separator` method returns a string representing the separator line.
19
+ # - The `total` method prints the total duration.
20
+ #
21
+ # @see #header
22
+ # @see #process_time_entries
23
+ # @see #separator
24
+ # @see #total
25
+ def table
26
+ header
27
+ time_block, duration_by_tag = process_time_entries
28
+ puts separator
29
+ total
30
+ [time_block, duration_by_tag]
31
+ end
32
+
33
+ # Formats the header of the time tracking report table.
34
+ #
35
+ # @return [void] This method does not return a value; it performs side effects such as printing
36
+ # the formatted header.
37
+ #
38
+ # @example Format and print the table header
39
+ # header
40
+ #
41
+ # @note The method constructs a string representing the table header and prints it.
42
+ def header
43
+ title = "Tracked time report [#{@filter.blink.red}]:"
44
+ header = <<~TABLE
45
+ #{title}
46
+ #{separator}
47
+ \033[32m| Id | Date | Tag | Start | End | Duration | Notes |\033[0m
48
+ #{separator}
49
+ TABLE
50
+ puts header
51
+ end
52
+
53
+ # Formats the separator line for the time tracking report table.
54
+ #
55
+ # @return [String] The formatted separator line.
56
+ #
57
+ # @example Get the formatted table separator
58
+ # separator # => '+-------+------------+--------+----------+----------+----------+------------+'
59
+ #
60
+ # @note The method returns a string representing the separator line for the table.
61
+ def separator
62
+ '+-------+------------+--------+----------+----------+----------+--------------------+'
63
+ end
64
+
65
+ # Processes each time entry in the `items` array and updates the time block and duration by tag.
66
+ #
67
+ # @return [Array<(Hash, Hash)>] An array containing the updated time block and duration by tag.
68
+ #
69
+ # @example
70
+ # items = [
71
+ # [start_time1, end_time1, tag1],
72
+ # [start_time2, end_time2, tag2]
73
+ # ]
74
+ # process_time_entries
75
+ # #=> [{ '2024-10-21' => { 8 => [duration1, tag1], 9 => [duration2, tag2] } },
76
+ # { tag1 => total_duration1, tag2 => total_duration2 }]
77
+ #
78
+ # @note
79
+ # - The method relies on the `items` instance variable, which should be an array of arrays.
80
+ # - Each sub-array in `items` is expected to contain a start time, end time, and a tag.
81
+ # - The `display_time_entry` method is used to display each time entry.
82
+ # - The `process_time_block_item` method processes each time entry and updates the time block and duration by tag.
83
+ #
84
+ # @see #items
85
+ # @see #display_time_entry
86
+ # @see #process_time_block_item
87
+ def process_time_entries
88
+ duration_by_tag = Hash.new(0)
89
+ time_block = Hash.new { |hash, key| hash[key] = {} }
90
+
91
+ items.each_with_index do |item, idx|
92
+ display_time_entry(item, TimeHelper.extract_date(items, idx))
93
+ time_block, duration_by_tag = process_time_block_item(item, time_block, duration_by_tag)
94
+ end
95
+ [time_block, duration_by_tag]
96
+ end
97
+
98
+ # Processes a time block item and updates the time block hash.
99
+ #
100
+ # @param item [Array] The time entry to process, containing the start time, end time, and tag.
101
+ # @param time_block [Hash] A hash containing time block data, where keys are dates and values are hashes of time
102
+ # slots and their corresponding values.
103
+ # @param duration_by_tag [Hash] A hash containing the total duration by tag.
104
+ #
105
+ # @return [Array<(Hash, Hash)>] An array containing the updated time block hash and the updated duration
106
+ # by tag hash.
107
+ #
108
+ # @example
109
+ # item = [nil, Time.new(2024, 10, 21, 8, 0, 0), Time.new(2024, 10, 21, 9, 0, 0), 'work']
110
+ # time_block = {}
111
+ # duration_by_tag = {}
112
+ # process_time_block_item(item, time_block, duration_by_tag)
113
+ # #=> [{ '2024-10-21' => { 8 => [3600, 'work'] } }, { 'work' => 3600 }]
114
+ #
115
+ # @note
116
+ # - The method relies on the `TimeHelper` module for time-related calculations.
117
+ # - The `add_hashes` method is used to merge the new time block data into the existing time block hash.
118
+ # - The `calculate_duration` method calculates the duration between the start and end times.
119
+ #
120
+ # @see TimeHelper#count_seconds_per_hour_block
121
+ # @see TimeHelper#timestamp_to_date
122
+ # @see TimeHelper#calculate_duration
123
+ # @see #add_hashes
124
+ def process_time_block_item(item, time_block, duration_by_tag)
125
+ _, start_time, end_time, tag = item
126
+
127
+ block_hour = TimeHelper.count_seconds_per_hour_block(start_time, end_time, tag)
128
+ date_line = TimeHelper.timestamp_to_date(start_time)
129
+ time_block[date_line] = add_hashes(time_block[date_line], block_hour)
130
+ duration_by_tag[tag] += TimeHelper.calculate_duration(start_time, end_time)
131
+ [time_block, duration_by_tag]
132
+ end
133
+
134
+ # Displays a single time entry in the report.
135
+ #
136
+ # @param item [Array] The item to display.
137
+ # @param date [String, nil] The date to display. If nil, the date is not displayed.
138
+ #
139
+ # @return [void] This method does not return a value; it performs side effects such as printing the row.
140
+ #
141
+ # @example Display a time entry
142
+ # display_time_entry(item, '2021-10-01')
143
+ #
144
+ # @note The method formats and prints the row for the time entry.
145
+ def display_time_entry(item, date = nil)
146
+ return puts 'Missing time entry data.' unless item
147
+
148
+ id, start_time_value, end_time_value, tag_name, notes = item
149
+ duration = TimeHelper.calculate_duration(start_time_value, end_time_value)
150
+ start_time = TimeHelper.format_time(start_time_value)
151
+ end_time = TimeHelper.format_time(end_time_value)
152
+ start_date = date || (' ' * 10)
153
+ puts format_table_row(id, tag_name[0..5], start_date, start_time, end_time, duration, notes)
154
+ end
155
+
156
+ # Formats a table row with the given row data.
157
+ #
158
+ # @param row [Array] The row data to format, containing the following elements:
159
+ # - id [Integer] The ID of the time entry.
160
+ # - tag [String] The tag associated with the time entry.
161
+ # - start_date [String] The start date of the time entry.
162
+ # - start_time [String] The start time of the time entry.
163
+ # - end_time [String] The end time of the time entry.
164
+ # - duration [Integer] The duration of the time entry in seconds.
165
+ # - notes [String] Any notes associated with the time entry.
166
+ #
167
+ # @return [String] The formatted table row.
168
+ #
169
+ # @example
170
+ # row = [1, 'work', '2024-10-21', '08:00:00', '09:00:00', 3600, 'Completed task A']
171
+ # format_table_row(*row)
172
+ # #=> "| 1| 2024-10-21 | work | 08:00:00 | 09:00:00 | 1:00:00 | Completed task A |"
173
+ #
174
+ # @note
175
+ # - The method relies on the `@db` instance variable, which should be an object with `find_item`
176
+ # and `seconds_to_hms` methods.
177
+ # - The `format_end_time`, `format_mark`, and `format_notes` methods are used to format specific parts of the row.
178
+ #
179
+ # @see #format_end_time
180
+ # @see #format_mark
181
+ # @see #format_notes
182
+ def format_table_row(*row)
183
+ id, tag, start_date, start_time, end_time, duration, notes = row
184
+ end_time = format_end_time(end_time, id, duration)
185
+ mark = format_mark(id)
186
+
187
+ "| #{id.to_s.rjust(6)}| #{start_date} | #{tag.ljust(6)} | #{start_time.split[1]} | " \
188
+ "#{end_time.rjust(8)} | #{@db.seconds_to_hms(duration).rjust(8)} | #{format_notes(notes)} #{mark}"
189
+ end
190
+
191
+ # Formats the end time of the time entry.
192
+ #
193
+ # @param end_time [String] The end time of the time entry.
194
+ # @param id [Integer] The ID of the time entry.
195
+ # @param duration [Integer] The duration of the time entry in seconds.
196
+ #
197
+ # @return [String] The formatted end time.
198
+ #
199
+ # @example
200
+ # format_end_time('09:00:00', 1, 3600)
201
+ # #=> "09:00:00"
202
+ #
203
+ # @note
204
+ # - The method relies on the `@db` instance variable, which should be an object with a `find_item` method.
205
+ # - If the `pomodoro` value is positive and the end time is not set, a blinking `timet` is added.
206
+ #
207
+ # @see #format_table_row
208
+ def format_end_time(end_time, id, duration)
209
+ end_time = end_time ? end_time.split[1] : '-'
210
+ pomodoro = @db.find_item(id)[5] || 0
211
+
212
+ if pomodoro.positive? && end_time == '-'
213
+ delta = (@db.find_item(id)[5] - (duration / 60.0)).round(1)
214
+ timet = "\e]8;;Session ends\a#{delta} min\e]8;;\a".green
215
+ end_time = " #{timet}".blink
216
+ end
217
+
218
+ end_time
219
+ end
220
+
221
+ # Formats the mark for the time entry.
222
+ #
223
+ # @param id [Integer] The ID of the time entry.
224
+ #
225
+ # @return [String] The formatted mark.
226
+ #
227
+ # @example
228
+ # format_mark(1)
229
+ # #=> "|"
230
+ #
231
+ # @note
232
+ # - The method relies on the `@db` instance variable, which should be an object with a `find_item` method.
233
+ # - If the `pomodoro` value is positive, a special mark is added.
234
+ #
235
+ # @see #format_table_row
236
+ def format_mark(id)
237
+ pomodoro = @db.find_item(id)[5] || 0
238
+ mark = '|'
239
+ mark = "#{'├'.white} #{'P'.blue.blink}" if pomodoro.positive?
240
+ mark
241
+ end
242
+
243
+ # Formats the notes column of the time tracking report table.
244
+ #
245
+ # @param notes [String, nil] The notes to be formatted.
246
+ # @return [String] The formatted notes.
247
+ #
248
+ # @example Format notes
249
+ # format_notes('This is a long note that needs to be truncated')
250
+ #
251
+ # @note The method truncates the notes to a maximum of 20 characters and pads them to a fixed width.
252
+ def format_notes(notes)
253
+ spaces = 17
254
+ return ' ' * spaces unless notes
255
+
256
+ max_length = spaces - 3
257
+ notes = "#{notes.slice(0, max_length)}..." if notes.length > max_length
258
+ notes.ljust(spaces)
259
+ end
260
+
261
+ # Displays the total duration of the tracked time entries.
262
+ #
263
+ # @return [void] This method does not return a value; it performs side effects such as printing the total duration.
264
+ #
265
+ # @example Display the total duration
266
+ # total
267
+ #
268
+ # @note The method calculates and prints the total duration of the tracked time entries.
269
+ def total
270
+ total = @items.map do |item|
271
+ TimeHelper.calculate_duration(item[1], item[2])
272
+ end.sum
273
+ puts "|#{' ' * 43}#{'Total:'.blue} | #{@db.seconds_to_hms(total).rjust(8).blue} |#{' ' * 20}|"
274
+ puts separator
275
+ display_pomodoro_label
276
+ end
277
+
278
+ # Displays a blinking "Pomodoro" label if the sum of the compacted values in the 6th column of @items is positive.
279
+ #
280
+ # @example
281
+ # display_pomodoro_label
282
+ #
283
+ # @return [void] This method returns nothing.
284
+ #
285
+ # @note
286
+ # - The method relies on the `@items` instance variable, which should be an array of arrays.
287
+ # - The 6th column of each sub-array in `@items` is expected to contain numeric values.
288
+ # - The method uses the `blue.blink` color formatting, which assumes the presence of a `String` extension or
289
+ # gem that supports color formatting.
290
+ #
291
+ # @see #@items
292
+ # @see String#blue
293
+ # @see String#blink
294
+ def display_pomodoro_label
295
+ return unless @items.map { |x| x[5] }.compact.sum.positive?
296
+
297
+ puts "#{'P'.blue.blink}omodoro"
298
+ end
299
+ end
300
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Timet
4
+ # The TagDistribution module provides functionality to format and display the distribution of tags based on their
5
+ # durations. This is particularly useful for visualizing how time is distributed across different tags in a project
6
+ # or task management system.
7
+ module TagDistribution
8
+ MAX_BAR_LENGTH = 70
9
+ BLOCK_CHAR = '▅'
10
+ TAG_SIZE = 12
11
+
12
+ # Formats and displays the tag distribution.
13
+ #
14
+ # @param duration_by_tag [Hash<String, Integer>] A hash where keys are tags and values are durations in seconds.
15
+ # @return [void] This method outputs the formatted tag distribution to the console.
16
+ #
17
+ # @example
18
+ # duration_by_tag = { "timet" => 3600, "nextjs" => 1800 }
19
+ # Formatter.format_tag_distribution(duration_by_tag)
20
+ # # Output:
21
+ # # timet: 66.67% ====================
22
+ # # nextjs: 33.33% ==========
23
+ def tag_distribution(duration_by_tag, colors)
24
+ total = duration_by_tag.values.sum
25
+ return unless total.positive?
26
+
27
+ sorted_duration_by_tag = duration_by_tag.sort_by { |_, duration| -duration }
28
+ process_and_print_tags(sorted_duration_by_tag, total, colors)
29
+ end
30
+
31
+ # Processes and prints the tag distribution information.
32
+ #
33
+ # @param sorted_duration_by_tag [Array<Array(String, Numeric)>] An array of arrays where each inner array contains a
34
+ # tag and its corresponding duration, sorted by duration in descending order.
35
+ # @param total [Numeric] The total duration of all tags combined.
36
+ # @return [void] This method outputs the tag distribution information to the standard output.
37
+ def process_and_print_tags(sorted_duration_by_tag, total, colors)
38
+ sorted_duration_by_tag.each do |tag, duration|
39
+ value, bar_length = calculate_value_and_bar_length(duration, total)
40
+ horizontal_bar = (BLOCK_CHAR * bar_length).to_s.color(colors[tag] + 1)
41
+ tag = tag[0..TAG_SIZE - 1] if tag.size >= TAG_SIZE
42
+ puts "#{tag.rjust(TAG_SIZE)}: #{value.to_s.rjust(5)}% #{horizontal_bar}"
43
+ end
44
+ end
45
+
46
+ # Calculates the percentage value and bar length for a given duration and total duration.
47
+ #
48
+ # @param duration [Numeric] The duration for the current tag.
49
+ # @param total [Numeric] The total duration.
50
+ # @return [Array<(Float, Integer)>] An array containing the calculated value and bar length.
51
+ #
52
+ # @example
53
+ # calculate_value_and_bar_length(50, 100, 2) #=> [50.0, 25]
54
+ def calculate_value_and_bar_length(duration, total)
55
+ value = duration.to_f / total
56
+ percentage_value = (duration.to_f / total * 100).round(2)
57
+ bar_length = (value * MAX_BAR_LENGTH).round
58
+ [percentage_value, bar_length]
59
+ end
60
+ end
61
+ end
@@ -3,7 +3,7 @@
3
3
  module Timet
4
4
  # This module is responsible for formatting the output of the `timet` application.
5
5
  # It provides methods for formatting the table header, separators, and rows.
6
- module Formatter
6
+ module TimeBlockChart
7
7
  CHAR_MAPPING = {
8
8
  0..120 => '_',
9
9
  121..450 => '▁',
@@ -16,123 +16,7 @@ module Timet
16
16
  3151..3600 => '█'
17
17
  }.freeze
18
18
 
19
- # Formats the header of the time tracking report table.
20
- #
21
- # @return [void] This method does not return a value; it performs side effects such as printing
22
- # the formatted header.
23
- #
24
- # @example Format and print the table header
25
- # format_table_header
26
- #
27
- # @note The method constructs a string representing the table header and prints it.
28
- def format_table_header
29
- title = "Tracked time report #{@filter.blink.red}]:"
30
- header = <<~TABLE
31
- #{title}
32
- #{format_table_separator}
33
- \033[32m| Id | Date | Tag | Start | End | Duration | Notes |\033[0m
34
- #{format_table_separator}
35
- TABLE
36
- puts header
37
- end
38
-
39
- # Formats the separator line for the time tracking report table.
40
- #
41
- # @return [String] The formatted separator line.
42
- #
43
- # @example Get the formatted table separator
44
- # format_table_separator # => '+-------+------------+--------+----------+----------+----------+------------+'
45
- #
46
- # @note The method returns a string representing the separator line for the table.
47
- def format_table_separator
48
- '+-------+------------+--------+----------+----------+----------+--------------------+'
49
- end
50
-
51
- # Formats a row of the time tracking report table.
52
- #
53
- # @param row [Array] The row data to be formatted.
54
- # @return [String] The formatted row.
55
- #
56
- # @example Format a table row
57
- # format_table_row(1, 'work', '2023-10-01', '12:00:00', '14:00:00', 7200, 'Completed task X')
58
- #
59
- # @note The method formats each element of the row and constructs a string representing the formatted row.
60
- def format_table_row(*row)
61
- id, tag, start_date, start_time, end_time, duration, notes = row
62
- "| #{id.to_s.rjust(5)} | #{start_date} | #{tag.ljust(6)} | #{start_time.split[1]} | " \
63
- "#{end_time.split[1].rjust(8)} | #{@db.seconds_to_hms(duration).rjust(8)} | #{format_notes(notes)} |"
64
- end
65
-
66
- # Formats the notes column of the time tracking report table.
67
- #
68
- # @param notes [String, nil] The notes to be formatted.
69
- # @return [String] The formatted notes.
70
- #
71
- # @example Format notes
72
- # format_notes('This is a long note that needs to be truncated')
73
- #
74
- # @note The method truncates the notes to a maximum of 20 characters and pads them to a fixed width.
75
- def format_notes(notes)
76
- spaces = 17
77
- return ' ' * spaces unless notes
78
-
79
- max_length = spaces - 3
80
- notes = "#{notes.slice(0, max_length)}..." if notes.length > max_length
81
- notes.ljust(spaces)
82
- end
83
-
84
- # @!method format_tag_distribution(duration_by_tag)
85
- # Formats and displays the tag distribution.
86
- #
87
- # @example
88
- # duration_by_tag = { "timet" => 3600, "nextjs" => 1800 }
89
- # Formatter.format_tag_distribution(duration_by_tag)
90
- # # Output:
91
- # # timet: 66.67% ====================
92
- # # nextjs: 33.33% ==========
93
- #
94
- # @param duration_by_tag [Hash<String, Integer>] A hash where keys are tags and values are durations in seconds.
95
- # @return [void] This method outputs the formatted tag distribution to the console.
96
- def format_tag_distribution(duration_by_tag, colors)
97
- total = duration_by_tag.values.sum
98
- return unless total.positive?
99
-
100
- factor = duration_by_tag.size < 3 ? 2 : 1
101
- sorted_duration_by_tag = duration_by_tag.sort_by { |_, duration| -duration }
102
- process_and_print_tags(sorted_duration_by_tag, factor, total, colors)
103
- end
104
-
105
- # Processes and prints the tag distribution information.
106
- #
107
- # @param sorted_duration_by_tag [Array<Array(String, Numeric)>] An array of arrays where each inner array contains a
108
- # tag and its corresponding duration, sorted by duration in descending order.
109
- # @param factor [Numeric] The factor used to adjust the bar length.
110
- # @param total [Numeric] The total duration of all tags combined.
111
- # @return [void] This method outputs the tag distribution information to the standard output.
112
- def process_and_print_tags(*args)
113
- sorted_duration_by_tag, factor, total, colors = args
114
- block = '▅'
115
- sorted_duration_by_tag.each do |tag, duration|
116
- value, bar_length = calculate_value_and_bar_length(duration, total, factor)
117
- horizontal_bar = (block * bar_length).to_s.color(colors[tag] + 1)
118
- puts "#{tag.rjust(8)}: #{value.to_s.rjust(7)}% #{horizontal_bar}"
119
- end
120
- end
121
-
122
- # Calculates the value and bar length for a given duration, total duration, and factor.
123
- #
124
- # @param duration [Numeric] The duration for the current tag.
125
- # @param total [Numeric] The total duration.
126
- # @param factor [Numeric] A factor to adjust the formatting.
127
- # @return [Array<(Float, Integer)>] An array containing the calculated value and bar length.
128
- #
129
- # @example
130
- # calculate_value_and_bar_length(50, 100, 2) #=> [50.0, 25]
131
- def calculate_value_and_bar_length(duration, total, factor)
132
- value = (duration.to_f / total * 100).round(2)
133
- bar_length = (value / factor).round
134
- [value, bar_length]
135
- end
19
+ SEPARATOR_CHAR = '░'
136
20
 
137
21
  # Prints a time block chart based on the provided time block and colors.
138
22
  #
@@ -186,23 +70,12 @@ module Timet
186
70
  puts '┌╴W ╴╴╴╴╴╴⏰╴╴╴╴╴╴┼'.gray + "#{'╴' * (24 - start_hour) * 4}╴╴╴┼".gray
187
71
  end
188
72
 
189
- # Prints the block characters for each hour in the time block chart.
190
- #
191
- # This method iterates over each hour from 0 to 23, retrieves the corresponding
192
- # block character using the `get_block_char` method, and prints it aligned for
193
- # readability. It also adds a double newline at the end for separation.
194
- #
195
- # @param time_block [Hash] A hash where the keys are formatted hour strings
196
- # (e.g., "00", "01") and the values are the corresponding
197
- # values to determine the block character.
198
- # @example
199
- # time_block = { "00" => 100, "01" => 200, ..., "23" => 300 }
200
- # print_blocks(time_block)
201
- # # Output:
202
- # # ▁ ▂ ▃ ▄ ▅ ▆ ▇ █ ▁ ▂ ▃ ▄ ▅ ▆ ▇ █ ▁ ▂ ▃ ▄ ▅ ▆ ▇ █
203
- # #
204
- # # (followed by two newlines)
73
+ # Prints the time blocks for each date in the given time block data structure.
205
74
  #
75
+ # @param time_block [Hash] A hash where keys are date strings and values are time block data.
76
+ # @param colors [Hash] A hash containing color codes for formatting.
77
+ # @param start_hour [Integer] The starting hour for the time blocks.
78
+ # @return [void]
206
79
  def print_blocks(time_block, colors, start_hour)
207
80
  return unless time_block
208
81
 
@@ -210,32 +83,83 @@ module Timet
210
83
  time_block.each_key do |date_string|
211
84
  date = Date.parse(date_string)
212
85
  day = date.strftime('%a')[0..1]
213
- weekend = date_string
214
- if %w[Sa Su].include?(day)
215
- day = day.red
216
- weekend = weekend.red
217
- end
218
86
 
219
- weeks << date.cweek
220
- n = weeks.size - 1
221
- week = if (weeks[n] == weeks[n - 1]) && n.positive?
222
- ' '
223
- else
224
- weeks[n].to_s.underline
225
- end
226
- puts "┆ ┆#{' ' * (24 - start_hour) * 4} ┼░░░░".gray if week != ' ' && n.positive?
227
- print '┆'.gray + "#{week} #{weekend} #{day} " + '┆- '.gray
87
+ format_and_print_date_info(date_string, day, weeks, start_hour)
88
+
228
89
  time_block_initial = time_block[date_string]
229
90
  print_time_blocks(start_hour, time_block_initial, colors)
230
91
 
231
- total_seconds = time_block_initial.values.map { |item| item[0] }.sum
232
- hours_per_day = (total_seconds / 3600.0).round(1)
233
- print "-┆#{hours_per_day}h".gray
234
- puts
92
+ calculate_and_print_hours(time_block_initial)
235
93
  end
236
94
  print_footer(start_hour)
237
95
  end
238
96
 
97
+ # Calculates the total hours from the given time block data and prints it.
98
+ #
99
+ # @param time_block_initial [Hash] A hash containing time block data for a specific date.
100
+ # @return [void]
101
+ def calculate_and_print_hours(time_block_initial)
102
+ total_seconds = time_block_initial.values.map { |item| item[0] }.sum
103
+ hours_per_day = (total_seconds / 3600.0).round(1)
104
+ print "-┆#{hours_per_day}h".gray
105
+ puts
106
+ end
107
+
108
+ # Formats and prints the date information including the week and day.
109
+ #
110
+ # @param date_string [String] The date string in a parsable format.
111
+ # @param day [String] The abbreviated day of the week (e.g., "Mo" for Monday).
112
+ # @param weeks [Array<Integer>] An array storing the week numbers.
113
+ # @param start_hour [Integer] The starting hour for the time blocks.
114
+ # @return [void]
115
+ def format_and_print_date_info(date_string, day, weeks, start_hour)
116
+ weekend = date_string
117
+ day = day.red if %w[Sa Su].include?(day)
118
+ weekend = weekend.red if %w[Sa Su].include?(day)
119
+
120
+ week = format_and_print_week(date_string, weeks, start_hour)
121
+
122
+ print '┆'.gray + "#{week} #{weekend} #{day} " + '┆- '.gray
123
+ end
124
+
125
+ # Formats and prints the week information including the separator if necessary.
126
+ #
127
+ # @param date_string [String] The date string in a parsable format.
128
+ # @param weeks [Array<Integer>] An array storing the week numbers.
129
+ # @param start_hour [Integer] The starting hour for the time blocks.
130
+ # @return [String] The formatted week string.
131
+ def format_and_print_week(date_string, weeks, start_hour)
132
+ week, current_index = determine_week(date_string, weeks)
133
+ print_separator(start_hour, week, current_index)
134
+ week
135
+ end
136
+
137
+ # Determines the week string based on the date and the previous week.
138
+ #
139
+ # @param date_string [String] The date string in a parsable format.
140
+ # @param weeks [Array<Integer>] An array storing the week numbers.
141
+ # @return [Array<String, Integer>] An array containing the formatted week string and the current index.
142
+ def determine_week(date_string, weeks)
143
+ weeks << Date.parse(date_string).cweek
144
+ current_index = weeks.size - 1
145
+ current_week = weeks[current_index]
146
+ week = current_week == weeks[current_index - 1] && current_index.positive? ? ' ' : current_week.to_s.underline
147
+ [week, current_index]
148
+ end
149
+
150
+ # Prints the separator line if the week string is not empty and the current index is positive.
151
+ #
152
+ # @param start_hour [Integer] The starting hour for the time blocks.
153
+ # @param week [String] The formatted week string.
154
+ # @param current_index [Integer] The current index in the weeks array.
155
+ # @return [void]
156
+ def print_separator(start_hour, week, current_index)
157
+ return unless week != ' ' && current_index.positive?
158
+
159
+ sep = SEPARATOR_CHAR
160
+ puts "┆#{sep * 17}┼#{sep * (24 - start_hour) * 4}#{sep * 3}┼#{sep * 4}".gray
161
+ end
162
+
239
163
  # Prints the footer of the report.
240
164
  #
241
165
  # @param start_hour [Integer] The start time used to calculate the footer length.