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
8 changes: 0 additions & 8 deletions backend/app/api/routers/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

from app.core.auth import get_current_user_id
from app.core.db import get_db
from app.schemas.availability import WeeklyAvailabilityUpdate
from app.schemas.user import AnySettingOut, SettingUpdate
from app.services import settings_service

Expand Down Expand Up @@ -34,11 +33,4 @@ async def update_settings(
session: Session = Depends(get_db),
) -> AnySettingOut:
"""Update a user setting."""
if setting.type == "schedule":
availability_update = WeeklyAvailabilityUpdate.model_validate(
{"windows": setting.value}
)
return settings_service.update_availability_setting(
user_id, availability_update, session
)
return settings_service.update_setting(user_id, setting, session)
73 changes: 72 additions & 1 deletion backend/app/services/settings_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
BooleanSettingOut,
BooleanSettingUpdate,
ScheduleSettingOut,
ScheduleSettingUpdate,
StringSettingOut,
StringSettingUpdate,
UserSettingsOut,
Expand Down Expand Up @@ -87,10 +88,20 @@ def get_all_user_settings(user_id: int, session: Session) -> UserSettingsOut:

def update_setting(
user_id: int,
setting: StringSettingUpdate | BooleanSettingUpdate,
setting: StringSettingUpdate | BooleanSettingUpdate | ScheduleSettingUpdate,
session: Session,
) -> AnySettingOut:
"""Update a setting and return the updated setting as output schema."""
if setting.type == "schedule":
availability_update = WeeklyAvailabilityUpdate.model_validate(
{"windows": setting.value}
)
return update_availability_setting(user_id, availability_update, session)
elif setting.key == "timezone":
old_timezone = setting_crud.get_user_setting(user_id, "timezone", session).value
new_timezone = setting.value
if old_timezone != new_timezone:
_reschedule(user_id, new_timezone, session)
updated_model = setting_crud.update_user_setting(user_id, setting, session)
return model_to_setting_out(updated_model)

Expand All @@ -108,6 +119,66 @@ def update_availability_setting(
return availability_to_setting_out(av_schema)


def _reschedule(user_id: int, new_timezone: str, session: Session):
from sqlmodel import select

from app.core.timezone import now_utc
from app.crud import availability_crud, schedule_item_crud, setting_crud, task_crud
from app.models.schedule_item import ScheduleItem
from app.services.greedy_scheduler import GreedyScheduler
from app.services.protocols import ChronoScheduler
from app.services.scheduling_utils import schedule_blocks_to_schedule_items

# Get all scheduled tasks
scheduled_tasks = task_crud.get_scheduled_tasks(user_id, session)

if not scheduled_tasks:
return # No tasks to reschedule

task_ids = [task.id for task in scheduled_tasks if task.id is not None]

existing_schedule_items = session.exec(
select(ScheduleItem)
.where(ScheduleItem.user_id == user_id)
.where(ScheduleItem.task_id.in_(task_ids)) # type: ignore[attr-defined]
.where(ScheduleItem.source == "task")
).all()

for item in existing_schedule_items:
session.delete(item)

session.flush()

availability = availability_crud.get_user_availability(user_id, session)
schedule_config = setting_crud.get_schedule_config(user_id, session)
schedule_config.timezone = new_timezone

all_schedule_items = schedule_item_crud.get_user_schedule_items(user_id, session)

scheduler: ChronoScheduler = GreedyScheduler()
response = scheduler.schedule_tasks(
scheduled_tasks,
all_schedule_items,
availability,
schedule_config,
)

if response.schedule_blocks:
schedule_items_to_create = schedule_blocks_to_schedule_items(
response.schedule_blocks, user_id
)
schedule_item_crud.create_schedule_items(schedule_items_to_create, session)

task_crud.update_tasks_scheduled_at(
task_ids,
now_utc(),
user_id,
session,
)

session.commit()


def get_setting_options(key: str) -> list[dict[str, str]] | None:
"""Get options for a setting key. Returns None if no options available."""
from app.services.option_factory_service import OPTION_FACTORIES
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/schedule/ScheduleItemsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
import { apiDownloadRequest, apiRequest, ApiError } from "@/lib/chrono-client";
import { useSchedule } from "@/context/schedule-context";
import { toast } from "sonner";
import { formatDuration, formatFloatingTime } from "@/lib/format-dt";
import { formatDateTime, formatDuration } from "@/lib/format-dt";

export default function ScheduleItemsList() {
const [scheduleItems, setScheduleItems] = useState<ScheduleItem[]>([]);
Expand Down Expand Up @@ -137,8 +137,8 @@ export default function ScheduleItemsList() {
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Calendar className="h-4 w-4" />
<span>
{formatFloatingTime(item.start_time)} -{" "}
{formatFloatingTime(item.end_time)}
{formatDateTime(item.start_time, item.user_timezone)} -{" "}
{formatDateTime(item.end_time, item.user_timezone)}
</span>
</div>
<div className="flex items-center gap-2 text-sm text-muted-foreground">
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/lib/format-dt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export const formatDuration = (startTime: string, endTime: string) => {
return mins > 0 ? `${hours}h ${mins}min` : `${hours}h`;
};

/**
TODO: this is more of a hack, for traveling its better to recalculate the time in the database.
export const formatFloatingTime = (dateString: string) => {
const literalTime = dateString.slice(0, 19);

Expand All @@ -31,3 +33,4 @@ export const formatFloatingTime = (dateString: string) => {
timeStyle: "short",
});
};
*/