Skip to content

Commit

Permalink
Add option for Monday-Friday week (#458)
Browse files Browse the repository at this point in the history
* feat: add option for differnt work weeks (Mon - Fri, Mon - Sat, Mon - Sun)

* chore: add comments for readability

Co-authored-by: Almar Klein <[email protected]>

* refactor: process free days only for stat_period = "1D"

* fix: show duration > 0 on free days

* chore: fix linting errors

* fix(target): fixed targets in relation to workdays

* refactor: improve maintainability

* fix: target wrong with scale smaller than 1D

---------

Co-authored-by: Almar Klein <[email protected]>
  • Loading branch information
staddi99 and almarklein committed Apr 4, 2024
1 parent 52d8a3a commit 5a33a3b
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 22 deletions.
24 changes: 20 additions & 4 deletions timetagger/app/dialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3798,6 +3798,12 @@ def open(self, callback=None):
<option value='1'>Monday</option>
<option value='6'>Saturday</option>
</select>
<div>Workdays:</div>
<select>
<option value='2'>Monday - Friday</option>
<option value='1'>Monday - Saturday</option>
<option value='0'>Monday - Sunday</option>
</select>
<div>Show time as:</div>
<select>
<option value='auto'>Auto</option>
Expand Down Expand Up @@ -3900,27 +3906,33 @@ def open(self, callback=None):
self._first_day_of_week.value = first_day_of_week
self._first_day_of_week.onchange = self._on_first_day_of_week_change

# Workdays
workdays = window.simplesettings.get("workdays")
self._workdays = self._repr_form.children[3]
self._workdays.value = workdays
self._workdays.onchange = self._on_workdays_change

# Time representation
time_repr = window.simplesettings.get("time_repr")
self._time_repr = self._repr_form.children[3]
self._time_repr = self._repr_form.children[5]
self._time_repr.value = time_repr
self._time_repr.onchange = self._on_time_repr_change

# Duration representation
duration_repr = window.simplesettings.get("duration_repr")
self._duration_repr = self._repr_form.children[5]
self._duration_repr = self._repr_form.children[7]
self._duration_repr.value = duration_repr
self._duration_repr.onchange = self._on_duration_repr_change

# Today snap time/offset
today_snap_offset = window.simplesettings.get("today_snap_offset")
self._today_snap_offset = self._repr_form.children[7]
self._today_snap_offset = self._repr_form.children[9]
self._today_snap_offset.value = today_snap_offset
self._today_snap_offset.onchange = self._on_today_snap_offset_change

# Today number of hours
today_end_offset = window.simplesettings.get("today_end_offset")
self._today_end_offset = self._repr_form.children[9]
self._today_end_offset = self._repr_form.children[11]
self._today_end_offset.value = today_end_offset
self._today_end_offset.onchange = self._on_today_end_offset_change

Expand Down Expand Up @@ -3961,6 +3973,10 @@ def _on_first_day_of_week_change(self):
first_day_of_week = int(self._first_day_of_week.value)
window.simplesettings.set("first_day_of_week", first_day_of_week)

def _on_workdays_change(self):
workdays = int(self._workdays.value)
window.simplesettings.set("workdays", workdays)

def _on_time_repr_change(self):
time_repr = self._time_repr.value
window.simplesettings.set("time_repr", time_repr)
Expand Down
44 changes: 44 additions & 0 deletions timetagger/app/dt.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,50 @@ def get_weeknumber(t):
return res # noqa


def get_remaining_hours_of_day(t):
d = Date(t * 1000)
return 24 - d.getHours() - d.getMinutes() / 60 - d.getSeconds() / 3600


def get_elapsed_hours_of_day(t):
d = Date(t * 1000)
return d.getHours() + d.getMinutes() / 60 + d.getSeconds() / 3600


def get_free_hours_in_range(t1, t2, free_days):
free_hours = 0
if free_days >= 1:
d1 = Date(t1 * 1000)
d2 = Date(t2 * 1000)
hours_in_range = (t2 - t1) / 3600

if hours_in_range > 24: # scale > "1D"
while d1 < d2:
if d1.getDay() == 0: # sunday
free_hours += 24
elif d1.getDay() == 6 and free_days == 2: # saturday
free_hours += 24
d1.setDate(d1.getDate() + 1) # next day
else: # scale <= "1D"
# starts on sunday
if d1.getDay() == 0 and d2.getDay() != 0:
free_hours = get_remaining_hours_of_day(t1)
# only sunday
elif d1.getDay() == 0 and d2.getDay() == 0:
free_hours = hours_in_range
# ends on sunday
elif d1.getDay() != 0 and d2.getDay() == 0 and free_days != 2:
free_hours = get_elapsed_hours_of_day(t2)
# starts on saturday
elif d1.getDay() == 6 and free_days == 2:
free_hours = hours_in_range
# ends on saturday
elif d1.getDay() != 6 and d2.getDay() == 6 and free_days == 2:
free_hours = get_elapsed_hours_of_day(t2)

return free_hours


if __name__ == "__main__":
import pscript

Expand Down
64 changes: 46 additions & 18 deletions timetagger/app/front.py
Original file line number Diff line number Diff line change
Expand Up @@ -2539,6 +2539,16 @@ def _draw_stats(self, ctx, t1, t2, x1, y1, x2, y2, stat_period, hover):
fullwidth = x2 - x1 # * (sumcount_full / (t2 - t1)) # ** 0.5
fullheight = (y2 - y1) * (sumcount_full / (t2 - t1)) # ** 0.5

# Darken the colors for free days.
if stat_period == "1D":
workdays_setting = window.simplesettings.get("workdays")
is_free_day = (text == "Sat" and workdays_setting == 2) or (
text == "Sun" and workdays_setting >= 1
)
if is_free_day:
ctx.fillStyle = COLORS.button_text_disabled
ctx.fillRect(x1, y1, fullwidth, y2 - y1)

# Show amount of time spend on each tag
x = x1
for i in range(len(stats_list)):
Expand All @@ -2562,15 +2572,16 @@ def _draw_stats(self, ctx, t1, t2, x1, y1, x2, y2, stat_period, hover):
# Draw big text in stronger color if it is the timerange containing today

# Draw duration at the left
ctx.fillStyle = COLORS.prim1_clr if hover else COLORS.prim2_clr
fontsizeleft = bigfontsize * (0.7 if selected_tags else 0.9)
ctx.font = f"{fontsizeleft}px {FONT.default}"
ctx.textBaseline = "bottom"
ctx.textAlign = "left"
duration_text = dt.duration_string(sumcount_selected, False)
if selected_tags:
duration_text += " / " + dt.duration_string(sumcount_nominal, False)
ctx.fillText(duration_text, x1 + 10, y2 - ymargin)
if not stat_period == "1D" or sumcount_nominal > 0 or not is_free_day:
ctx.fillStyle = COLORS.prim1_clr if hover else COLORS.prim2_clr
fontsizeleft = bigfontsize * (0.7 if selected_tags else 0.9)
ctx.font = f"{fontsizeleft}px {FONT.default}"
ctx.textBaseline = "bottom"
ctx.textAlign = "left"
duration_text = dt.duration_string(sumcount_selected, False)
if selected_tags:
duration_text += " / " + dt.duration_string(sumcount_nominal, False)
ctx.fillText(duration_text, x1 + 10, y2 - ymargin)

# Draw time-range indication at the right
isnow = t1 < self._canvas.now() < t2
Expand Down Expand Up @@ -3417,14 +3428,28 @@ def _draw_container(self, ctx, total_time, x1, y1, x2, y2):
ctx.fillText(duration, x_ref_duration, ty)
# -- Row for target
tx, ty = x_ref_labels, ymid - 2 + npixels * 0.85

# Select the target that best matches the current time range
best_target = None
m = {"day": 24, "week": 168, "month": 720, "year": 8760}
for period, divisor in m.items():
free_days_per_week = window.simplesettings.get("workdays")
free_hours_in_range = dt.get_free_hours_in_range(t1, t2, free_days_per_week)
work_hours_in_range = self._hours_in_range - free_hours_in_range
for period in ["day", "week", "month", "year"]:
target_hours = self._current_targets.get(period, 0)
if target_hours <= 0:
continue
factor = self._hours_in_range / divisor

# hours in period -> "day": 24, "week": 168, "month": 720, "year": 8760
if period == "day":
work_hours_in_period = 24
elif period == "week":
work_hours_in_period = 168 - free_days_per_week * 24
elif period == "month": # ~4.33 weeks in a month
work_hours_in_period = 720 - free_days_per_week * 24 * 4.33
elif period == "year":
work_hours_in_period = 8760 - free_days_per_week * 24 * 52

factor = work_hours_in_range / work_hours_in_period
if factor > 0.93 or not best_target:
best_target = {
"period": period,
Expand All @@ -3440,13 +3465,16 @@ def _draw_container(self, ctx, total_time, x1, y1, x2, y2):
if best_target:
done_this_period = total_time
target_this_period = 3600 * best_target.hours * best_target.factor
perc = 100 * done_this_period / target_this_period
prefix = "" if 0.93 < best_target.factor < 1.034 else "~ "
ctx.fillText(
f"{best_target.period} target at {prefix}{perc:0.0f}%",
tx,
ty,
)
if target_this_period > 0:
perc = 100 * done_this_period / target_this_period
ctx.fillText(
f"{best_target.period} target at {prefix}{perc:0.0f}%",
tx,
ty,
)
else:
ctx.fillText("No target", tx, ty)
left = target_this_period - done_this_period
left_s = dt.duration_string(abs(left), False)
left_prefix = "left" if left >= 0 else "over"
Expand Down
1 change: 1 addition & 0 deletions timetagger/app/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,7 @@ def __init__(self):
}
self._synced_keys = {
"first_day_of_week": 1,
"workdays": 0,
"time_repr": "auto",
"duration_repr": "hms",
"today_snap_offset": "",
Expand Down

0 comments on commit 5a33a3b

Please sign in to comment.