Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions TimeTrackerMCP_Server.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ def add_task(
recurring: bool = False,
frequency: str = "daily",
userdefined_days: int = 1,
priority: int = 0,
) -> str:
"""
Creates a new task inside an existing main project.
Expand All @@ -251,7 +252,11 @@ def add_task(
:param recurring: Whether the task repeats after it's marked done.
:param frequency: 'daily', 'business_days', 'weekly', 'monthly', or 'userdefined'. Only used if recurring is true.
:param userdefined_days: Number of days between occurrences. Only used if frequency is 'userdefined'.
:param priority: Priority from 0 (lowest, default) to 9 (highest).
"""
if not (0 <= priority <= 9):
return "Error: priority must be between 0 and 9."

tracker = get_tracker()
existing_project_names = [p['main_project_name'] for p in tracker.list_main_projects(status_filter='all')]
if main_project_name not in existing_project_names:
Expand All @@ -270,6 +275,7 @@ def add_task(
recurring=recurring,
frequency=frequency,
userdefined_days=userdefined_days,
priority=priority,
)
return f"Task '{task_name}' created in project '{main_project_name}'."

Expand Down Expand Up @@ -369,6 +375,7 @@ def update_task(
recurring: bool | None = None,
frequency: str | None = None,
userdefined_days: int | None = None,
priority: int | None = None,
) -> str:
"""
Updates one or more properties of an existing task in one call. Only the
Expand All @@ -384,7 +391,11 @@ def update_task(
:param recurring: Whether the task repeats after it's marked done.
:param frequency: 'daily', 'business_days', 'weekly', 'monthly', or 'userdefined'.
:param userdefined_days: Number of days between occurrences, for 'userdefined' frequency.
:param priority: Priority from 0 (lowest) to 9 (highest). Omit to keep the current one.
"""
if priority is not None and not (0 <= priority <= 9):
return "Error: priority must be between 0 and 9."

tracker = get_tracker()
tasks = tracker.list_tasks(main_project_name=main_project_name, status_filter='all')
current_task = next((t for t in tasks if t['task_name'] == task_name), None)
Expand Down Expand Up @@ -413,6 +424,7 @@ def update_task(
recurring=recurring,
frequency=frequency,
userdefined_days=userdefined_days,
priority=priority,
task_id=current_task.get('id'),
)
if success:
Expand Down
7 changes: 6 additions & 1 deletion TimeTrackerREST_Server.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# REST interface, the same way spyne is used for the SOAP interface.
try:
from fastapi import Depends, FastAPI, HTTPException
from pydantic import BaseModel
from pydantic import BaseModel, Field
import uvicorn
except ImportError:
print("Fehler: Die benötigten Bibliotheken sind nicht installiert.")
Expand Down Expand Up @@ -47,6 +47,7 @@ class Task(BaseModel):
recurring: bool
frequency: str
userdefined_days: int
priority: int


class InactiveProject(BaseModel):
Expand Down Expand Up @@ -102,6 +103,7 @@ class AddTaskRequest(BaseModel):
recurring: bool = False
frequency: str = "daily"
userdefined_days: int = 1
priority: int = Field(default=0, ge=0, le=9)


class UpdateTaskRequest(BaseModel):
Expand All @@ -113,6 +115,7 @@ class UpdateTaskRequest(BaseModel):
recurring: Optional[bool] = None
frequency: Optional[str] = None
userdefined_days: Optional[int] = None
priority: Optional[int] = Field(default=None, ge=0, le=9)


class MoveTaskRequest(BaseModel):
Expand Down Expand Up @@ -222,6 +225,7 @@ def add_task(main_project_name: str, body: AddTaskRequest, tracker: TimeTracker
body.recurring,
body.frequency,
body.userdefined_days,
body.priority,
)
return SuccessResult(success=created)

Expand Down Expand Up @@ -288,6 +292,7 @@ def update_task(main_project_name: str, task_name: str, body: UpdateTaskRequest,
body.recurring,
body.frequency,
body.userdefined_days,
body.priority,
task_id=task_id,
)
return SuccessResult(success=updated)
Expand Down
20 changes: 13 additions & 7 deletions TimeTrackerSOAP_Server.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class TaskModel(ComplexModel):
recurring = Boolean
frequency = Unicode
userdefined_days = Integer
priority = Integer

class InactiveProjectModel(ComplexModel):
main_project = Unicode
Expand Down Expand Up @@ -111,9 +112,9 @@ def list_completed_main_projects(ctx):

# --- Task Management ---

@rpc(Unicode, Unicode, Unicode, Boolean, Unicode, Boolean, Unicode, Integer, _returns=Boolean)
def add_task(ctx, main_project_name, task_name, due_date=None, today=False, note="", recurring=False, frequency="daily", userdefined_days=1):
return ctx.udc.add_task(main_project_name, task_name, due_date, today, note, recurring, frequency, userdefined_days)
@rpc(Unicode, Unicode, Unicode, Boolean, Unicode, Boolean, Unicode, Integer, Integer, _returns=Boolean)
def add_task(ctx, main_project_name, task_name, due_date=None, today=False, note="", recurring=False, frequency="daily", userdefined_days=1, priority=0):
return ctx.udc.add_task(main_project_name, task_name, due_date, today, note, recurring, frequency, userdefined_days, priority)

@rpc(Unicode, Unicode, Unicode, _returns=Array(TaskModel))
def list_tasks(ctx, main_project_name=None, status_filter='all', planning_filter=None):
Expand Down Expand Up @@ -153,11 +154,16 @@ def rename_task(ctx, main_project_name, old_name, new_name, task_id=None):
return ctx.udc.rename_task(main_project_name, old_name, new_name, task_id=task_id)
return ctx.udc.rename_task(main_project_name, old_name, new_name)

@rpc(Unicode, Unicode, Unicode, Unicode, Boolean, Unicode, Unicode, Boolean, Unicode, Integer, Integer, _returns=Boolean)
def update_task(ctx, main_project_name, old_name, new_name=None, due_date=None, today=None, note=None, status=None, recurring=None, frequency=None, userdefined_days=None, task_id=None):
@rpc(Unicode, Unicode, Unicode, Unicode, Boolean, Unicode, Unicode, Boolean, Unicode, Integer, Integer, Integer, _returns=Boolean)
def update_task(ctx, main_project_name, old_name, new_name=None, due_date=None, today=None, note=None, status=None, recurring=None, frequency=None, userdefined_days=None, task_id=None, priority=None):
# priority is appended after task_id (rather than grouped with the
# other content fields before it) so existing positional callers that
# already pass task_id as the 11th argument aren't shifted - spyne
# dispatches @rpc args purely by position, so inserting a new
# parameter anywhere but the end would silently break them.
if task_id is not None:
return ctx.udc.update_task(main_project_name, old_name, new_name, due_date, today, note, status, recurring, frequency, userdefined_days, task_id=task_id)
return ctx.udc.update_task(main_project_name, old_name, new_name, due_date, today, note, status, recurring, frequency, userdefined_days)
return ctx.udc.update_task(main_project_name, old_name, new_name, due_date, today, note, status, recurring, frequency, userdefined_days, priority=priority, task_id=task_id)
return ctx.udc.update_task(main_project_name, old_name, new_name, due_date, today, note, status, recurring, frequency, userdefined_days, priority=priority)

@rpc(Unicode, Unicode, Unicode, Unicode, _returns=OperationResultModel)
def move_task(ctx, old_main, task_name, new_main, task_id=None):
Expand Down
88 changes: 69 additions & 19 deletions sl/SL_Menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,13 +659,14 @@ def view_task_planning():
if is_active: display_name = f"**{display_name}**"
today_info = " ⭐" if task.get('today') else ""
recurring_info = " ↻" if task.get('recurring') else ""
priority_info = f" 🔺{task.get('priority', 0)}" if task.get('priority', 0) > 0 else ""
if is_active:
bullet = "🔨"
elif is_done:
bullet = "✔"
else:
bullet = "-"
st.markdown(f"<span style='display: inline-block; width: 2rem;'>{bullet}</span> **{task['main_project_name']}**: {display_name}{today_info}{recurring_info}", unsafe_allow_html=True)
st.markdown(f"<span style='display: inline-block; width: 2rem;'>{bullet}</span> **{task['main_project_name']}**: {display_name}{today_info}{recurring_info}{priority_info}", unsafe_allow_html=True)
with col_start_btn:
if st.button("▶", key=f"start_task_planning_weekly_{task['main_project_name']}_{task['task_name']}_{t_idx}", help=_("Start work on task"), disabled=is_active or task.get('status') == 'done'):
st.session_state.tracker.start_work(task['main_project_name'], task_id=task.get('id'))
Expand Down Expand Up @@ -744,13 +745,14 @@ def view_task_planning():
due_info = f" ({_('Due')}: {task['due_date']})" if task.get('due_date') else ""
today_info = " ⭐" if task.get('today') else ""
recurring_info = " ↻" if task.get('recurring') else ""
priority_info = f" 🔺{task.get('priority', 0)}" if task.get('priority', 0) > 0 else ""
if is_active:
bullet = "🔨"
elif is_done:
bullet = "✔"
else:
bullet = "-"
st.markdown(f"<span style='display: inline-block; width: 2rem;'>{bullet}</span> {display_name}{due_info}{today_info}{recurring_info}", unsafe_allow_html=True)
st.markdown(f"<span style='display: inline-block; width: 2rem;'>{bullet}</span> {display_name}{due_info}{today_info}{recurring_info}{priority_info}", unsafe_allow_html=True)
with col_start_btn:
if st.button("▶", key=f"start_task_planning_{main_proj_name}_{task['task_name']}_{t_idx}", help=_("Start work on task"), disabled=is_active or status == 'done'):
st.session_state.tracker.start_work(task['main_project_name'], task_id=task.get('id'))
Expand Down Expand Up @@ -826,6 +828,19 @@ def view_today_tasks():
key="today_show_only_open",
)
st.session_state.today_show_only_open_value = show_only_open

# Same session-state-mirroring reasoning as today_show_only_open_value
# above: a plain checkbox key would forget its value across a trip to
# the edit-task form and back.
if "today_sort_by_priority_value" not in st.session_state:
st.session_state.today_sort_by_priority_value = False
sort_by_priority = st.checkbox(
_("Sort by priority"),
value=st.session_state.today_sort_by_priority_value,
key="today_sort_by_priority",
)
st.session_state.today_sort_by_priority_value = sort_by_priority

today_tasks = [t for t in today_tasks_all if t.get('status') != 'done'] if show_only_open else today_tasks_all

if today_tasks:
Expand All @@ -836,7 +851,15 @@ def view_today_tasks():
if main_proj not in today_tasks_grouped:
today_tasks_grouped[main_proj] = []
today_tasks_grouped[main_proj].append(task)


if sort_by_priority:
# Sorted within each project group rather than flattened across
# all of them, so the existing per-project grouping/expanders
# stay intact - a stable sort keeps same-priority tasks in their
# original relative order.
for tasks_in_group in today_tasks_grouped.values():
tasks_in_group.sort(key=lambda t: t.get('priority', 0), reverse=True)

# Each project's tasks are shown inside a collapsible expander so
# projects with many tasks don't crowd out the rest of the list.
# on_change="rerun" is required for st.expander to track its state in
Expand All @@ -861,7 +884,7 @@ def view_today_tasks():
# of leaving it open on the way to/from editing a task.
try:
for t_idx, task in enumerate(sub_tasks): # Iterate through tasks in the group
col_task, col_start_btn, col_edit_btn, col_done_btn = st.columns([10, 1, 1, 1])
col_task, col_priority, col_start_btn, col_edit_btn, col_done_btn = st.columns([7, 2, 1, 1, 1])
with col_task:
name = task['task_name']
status = task.get('status')
Expand All @@ -871,13 +894,33 @@ def view_today_tasks():
if is_active: display_name = f"**{display_name}**"
due_info = f" ({_('Due')}: {task['due_date']})" if task.get('due_date') else ""
recurring_info = " ↻" if task.get('recurring') else ""
priority_info = f" 🔺{task.get('priority', 0)}" if task.get('priority', 0) > 0 else ""
if is_active:
bullet = "🔨"
elif is_done:
bullet = "✔"
else:
bullet = "-"
st.markdown(f"<span style='display: inline-block; width: 2rem;'>{bullet}</span> {display_name}{due_info}{recurring_info}", unsafe_allow_html=True)
st.markdown(f"<span style='display: inline-block; width: 2rem;'>{bullet}</span> {display_name}{due_info}{recurring_info}{priority_info}", unsafe_allow_html=True)
with col_priority:
new_priority = st.number_input(
_("Priority"), min_value=0, max_value=9,
value=task.get('priority', 0), step=1,
key=f"today_priority_{task['main_project_name']}_{task['task_name']}_{t_idx}",
label_visibility="collapsed", help=_("0 (lowest) to 9 (highest)"),
)
if new_priority != task.get('priority', 0):
st.session_state.tracker.update_task(
task['main_project_name'],
task['task_name'],
due_date=task.get('due_date'),
recurring=task.get('recurring'),
frequency=task.get('frequency'),
userdefined_days=task.get('userdefined_days'),
priority=new_priority,
task_id=task.get('id'),
)
st.rerun()
with col_start_btn:
if st.button("▶", key=f"start_today_task_{task['main_project_name']}_{task['task_name']}_{t_idx}", help=_("Start work on task"), disabled=is_active or status == 'done'):
st.session_state.tracker.start_work(task['main_project_name'], task_id=task.get('id'))
Expand Down Expand Up @@ -1901,7 +1944,8 @@ def view_list_tasks():
status_text = f"({_('closed')})" if t['status'] == 'closed' else ""
display_name = f"{name} (done)" if t['status'] == 'done' else name
recurring_info = " ↻" if t.get('recurring') else ""
st.markdown(f"- {display_name} {status_text}{recurring_info}")
priority_info = f" 🔺{t.get('priority', 0)}" if t.get('priority', 0) > 0 else ""
st.markdown(f"- {display_name} {status_text}{recurring_info}{priority_info}")
else:
st.info(_("No tasks found for '{name}'.").format(name=selected_main))

Expand Down Expand Up @@ -2186,7 +2230,7 @@ def view_add_task_form():
if "new_task_note" not in st.session_state:
st.session_state.new_task_note = ""

col_date, col_today, col_rec = st.columns([2, 1, 1])
col_date, col_today, col_rec, col_prio = st.columns([2, 1, 1, 1])
with col_date:
due_date = st.date_input(_("Due date"), value=datetime.now().date(), format="YYYY-MM-DD")
with col_today:
Expand All @@ -2195,6 +2239,8 @@ def view_add_task_form():
with col_rec:
st.markdown("<div style='padding-top: 28px;'></div>", unsafe_allow_html=True)
is_recurring = st.checkbox(_("Recurring"))
with col_prio:
priority = st.number_input(_("Priority"), min_value=0, max_value=9, value=0, step=1, help=_("0 (lowest) to 9 (highest)"))

validation_error = is_recurring and not due_date
if validation_error:
Expand Down Expand Up @@ -2240,14 +2286,15 @@ def view_add_task_form():
elif not name:
st.error(_("Please enter a name."))
elif st.session_state.tracker.add_task(
main_project,
name,
due_date.isoformat() if due_date else None,
today,
main_project,
name,
due_date.isoformat() if due_date else None,
today,
st.session_state.new_task_note,
recurring=is_recurring,
frequency=final_freq,
userdefined_days=ud_days
userdefined_days=ud_days,
priority=priority
):
set_feedback(_("Task '{sub_name}' added to '{main_name}'.").format(sub_name=name, main_name=main_project))
if "new_task_note" in st.session_state: del st.session_state.new_task_note
Expand Down Expand Up @@ -2357,13 +2404,15 @@ def view_edit_task_form():
st.session_state.edit_due_date = None
st.rerun()

col_today, col_done, col_rec = st.columns(3)
col_today, col_done, col_rec, col_prio = st.columns(4)
with col_today:
is_today = st.checkbox(_("Today"), value=task_details.get('today', False))
with col_done:
is_done = st.checkbox(_("Done"), value=(task_details.get('status') == 'done'))
with col_rec:
is_recurring = st.checkbox(_("Recurring"), value=task_details.get('recurring', False))
with col_prio:
priority = st.number_input(_("Priority"), min_value=0, max_value=9, value=task_details.get('priority', 0), step=1, help=_("0 (lowest) to 9 (highest)"))

validation_error = is_recurring and not st.session_state.edit_due_date
if validation_error:
Expand Down Expand Up @@ -2410,16 +2459,17 @@ def view_edit_task_form():
new_status = 'done' if is_done else 'open'

if st.session_state.tracker.update_task(
main_project,
task_name,
new_name,
final_due,
is_today,
st.session_state.edit_task_note,
main_project,
task_name,
new_name,
final_due,
is_today,
st.session_state.edit_task_note,
new_status,
recurring=is_recurring,
frequency=final_freq,
userdefined_days=ud_days,
priority=priority,
task_id=task_id
):
set_feedback(_("Task updated successfully."))
Expand Down
Loading
Loading