Add event colors (Catppuccin Mocha) and read-only week view for users
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 20s

Admins can now pick a color per event from a Catppuccin Mocha palette;
existing events default to blue. Colors flow through to both the admin
timeline and the new "Неделя" tab in the user view, which reuses the
admin's weekly grid in a read-only mode. Also reworks event blocks to
show a single-line "start–end title" so short events no longer overflow,
tidies the admin toolbar, and expands mobile responsiveness across both
frontends.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
vrubel 2026-07-12 18:03:21 +00:00
parent fc66954c03
commit c40a6569b4
10 changed files with 685 additions and 64 deletions

View file

@ -115,7 +115,8 @@ async def create_item(
start_time=event.start_time,
duration_min=event.duration_min,
title=event.title,
repeat_weekly=event.repeat_weekly
repeat_weekly=event.repeat_weekly,
color=event.color or "blue"
)
if event.repeat_weekly:
new_event.weekday = get_weekday_from_date(event.date)
@ -214,7 +215,8 @@ async def update_item(
replacement_title=update.title or event.title,
replacement_date=replacement_date,
replacement_start_time=replacement_start_time,
replacement_duration_min=replacement_duration
replacement_duration_min=replacement_duration,
replacement_color=update.color or event.color
))
await db.commit()
return {"success": True, "action": "exception_created"}
@ -237,6 +239,8 @@ async def update_item(
event.start_time = update.start_time
if update.duration_min:
event.duration_min = update.duration_min
if update.color:
event.color = update.color
await db.commit()
return {"success": True}

View file

@ -25,7 +25,7 @@ class Task(Base):
class Event(Base):
__tablename__ = "events"
id = Column(Integer, primary_key=True, index=True)
date = Column(String, index=True) # YYYY-MM-DD
start_time = Column(String) # HH:MM
@ -33,6 +33,7 @@ class Event(Base):
title = Column(String, nullable=False)
repeat_weekly = Column(Boolean, default=False)
weekday = Column(Integer) # 0=Monday, 6=Sunday, только для weekly events
color = Column(String, default="blue") # ключ цвета из палитры Catppuccin Mocha
class WeeklyTaskException(Base):
@ -58,6 +59,7 @@ class EventException(Base):
replacement_date = Column(String)
replacement_start_time = Column(String)
replacement_duration_min = Column(Integer)
replacement_color = Column(String)
class TelegramUser(Base):
@ -85,6 +87,9 @@ async def _run_migrations(conn):
await conn.execute(text("ALTER TABLE events ADD COLUMN repeat_weekly BOOLEAN DEFAULT 0"))
if "weekday" not in event_columns:
await conn.execute(text("ALTER TABLE events ADD COLUMN weekday INTEGER"))
if "color" not in event_columns:
await conn.execute(text("ALTER TABLE events ADD COLUMN color TEXT DEFAULT 'blue'"))
await conn.execute(text("UPDATE events SET color = 'blue' WHERE color IS NULL"))
exception_columns = table_columns.get("weekly_task_exceptions", set())
if "task_id" not in exception_columns:
@ -92,6 +97,10 @@ async def _run_migrations(conn):
if "replacement_date" not in exception_columns:
await conn.execute(text("ALTER TABLE weekly_task_exceptions ADD COLUMN replacement_date TEXT"))
event_exception_columns = table_columns.get("event_exceptions", set())
if "replacement_color" not in event_exception_columns:
await conn.execute(text("ALTER TABLE event_exceptions ADD COLUMN replacement_color TEXT"))
async def _get_table_columns(conn):
table_names = ["tasks", "events", "weekly_task_exceptions", "event_exceptions"]

View file

@ -12,6 +12,7 @@ class EventCreate(BaseModel):
duration_min: int
title: str
repeat_weekly: bool = False
color: str = "blue" # ключ цвета из палитры Catppuccin Mocha
class TaskResponse(BaseModel):
id: int
@ -28,6 +29,7 @@ class EventResponse(BaseModel):
title: str
kind: Literal["event"] = "event"
repeat_weekly: bool = False
color: str = "blue"
class ScheduleItem(BaseModel):
kind: Literal["task", "event"]
@ -38,6 +40,7 @@ class ScheduleItem(BaseModel):
start_time: Optional[str] = None
duration_min: Optional[int] = None
repeat_weekly: Optional[bool] = None
color: Optional[str] = None
class ScheduleResponse(BaseModel):
items: list[ScheduleItem]
@ -49,3 +52,4 @@ class UpdateRequest(BaseModel):
duration_min: Optional[int] = None
occurrence_date: Optional[str] = None
scope: Optional[Literal["one_date", "series"]] = None # для weekly tasks
color: Optional[str] = None

View file

@ -102,7 +102,8 @@ async def materialize_events(
title=event.title,
start_time=event.start_time,
duration_min=event.duration_min,
repeat_weekly=False
repeat_weekly=False,
color=event.color or "blue"
))
result = await db.execute(select(Event).where(Event.repeat_weekly == True))
@ -146,7 +147,8 @@ async def materialize_events(
title=exception.replacement_title or event.title,
start_time=exception.replacement_start_time or event.start_time,
duration_min=exception.replacement_duration_min or event.duration_min,
repeat_weekly=True
repeat_weekly=True,
color=exception.replacement_color or event.color or "blue"
))
continue
@ -158,7 +160,8 @@ async def materialize_events(
title=event.title,
start_time=event.start_time,
duration_min=event.duration_min,
repeat_weekly=True
repeat_weekly=True,
color=event.color or "blue"
))
current_date += timedelta(days=1)