UMBRA/backend/app/schemas/person.py
Kyle Pope cb9f74a387 Entity pages enhancement: backend model extensions, shared components, Locations rebuild, panel animations
- Add migrations 019/020: extend Person (first/last name, nickname, is_favourite, company, job_title, mobile, category) and Location (is_frequent, contact_number, email)
- Update Person/Location models, schemas, and routers with new fields + name denormalisation
- Create shared component library: EntityTable, EntityDetailPanel, CategoryFilterBar, CopyableField, CategoryAutocomplete, useTableVisibility hook
- Rebuild LocationsPage: table layout with sortable columns, detail side panel, category filter bar, frequent pinned section
- Extend LocationForm with contact number, email, frequent toggle, category autocomplete
- Add animated panel transitions to ProjectDetail (55/45 split with cubic-bezier easing)
- Update TypeScript interfaces for Person and Location

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 21:10:26 +08:00

67 lines
1.9 KiB
Python

from pydantic import BaseModel, ConfigDict, model_validator
from datetime import datetime, date
from typing import Optional
class PersonCreate(BaseModel):
name: Optional[str] = None
first_name: Optional[str] = None
last_name: Optional[str] = None
nickname: Optional[str] = None
email: Optional[str] = None
phone: Optional[str] = None
mobile: Optional[str] = None
address: Optional[str] = None
birthday: Optional[date] = None
category: Optional[str] = None
is_favourite: bool = False
company: Optional[str] = None
job_title: Optional[str] = None
notes: Optional[str] = None
@model_validator(mode='after')
def require_some_name(self) -> 'PersonCreate':
if not any([self.name, self.first_name, self.last_name, self.nickname]):
raise ValueError('At least one name field is required')
return self
class PersonUpdate(BaseModel):
name: Optional[str] = None
first_name: Optional[str] = None
last_name: Optional[str] = None
nickname: Optional[str] = None
email: Optional[str] = None
phone: Optional[str] = None
mobile: Optional[str] = None
address: Optional[str] = None
birthday: Optional[date] = None
category: Optional[str] = None
is_favourite: Optional[bool] = None
company: Optional[str] = None
job_title: Optional[str] = None
notes: Optional[str] = None
class PersonResponse(BaseModel):
id: int
name: str
first_name: Optional[str]
last_name: Optional[str]
nickname: Optional[str]
email: Optional[str]
phone: Optional[str]
mobile: Optional[str]
address: Optional[str]
birthday: Optional[date]
category: Optional[str]
relationship: Optional[str]
is_favourite: bool
company: Optional[str]
job_title: Optional[str]
notes: Optional[str]
created_at: datetime
updated_at: datetime
model_config = ConfigDict(from_attributes=True)