""" Formatting utilities for NimbusFlow CLI. """ from typing import Optional from datetime import datetime from backend.models.enums import ScheduleStatus # ANSI color codes for table formatting class TableColors: RESET = '\033[0m' BOLD = '\033[1m' DIM = '\033[2m' BLUE = '\033[94m' GREEN = '\033[92m' YELLOW = '\033[93m' RED = '\033[91m' CYAN = '\033[96m' WHITE = '\033[97m' GREY = '\033[90m' # Table-specific colors HEADER = '\033[1m\033[96m' # Bold Cyan SUCCESS = '\033[1m\033[92m' # Bold Green ERROR = '\033[1m\033[91m' # Bold Red WARNING = '\033[1m\033[93m' # Bold Yellow BORDER = '\033[90m' # Grey INPUT_BOX = '\033[90m' # Grey (for input styling) def format_member_row(member, classification_name: Optional[str] = None) -> str: """Format a member for table display with colors.""" # Color coding for active status if member.IsActive: active = f"{TableColors.SUCCESS}✓ Active{TableColors.RESET}" name_color = TableColors.BOLD else: active = f"{TableColors.DIM}✗ Inactive{TableColors.RESET}" name_color = TableColors.DIM classification = classification_name or "N/A" email = member.Email or f"{TableColors.DIM}N/A{TableColors.RESET}" return (f"{TableColors.CYAN}{member.MemberId:3d}{TableColors.RESET} {TableColors.BORDER}│{TableColors.RESET} " f"{name_color}{member.FirstName:<12}{TableColors.RESET} {TableColors.BORDER}│{TableColors.RESET} " f"{name_color}{member.LastName:<15}{TableColors.RESET} {TableColors.BORDER}│{TableColors.RESET} " f"{TableColors.YELLOW}{classification:<12}{TableColors.RESET} {TableColors.BORDER}│{TableColors.RESET} " f"{active:<19} {TableColors.BORDER}│{TableColors.RESET} {email}") def format_schedule_row(schedule, member_name: str = "", service_info: str = "") -> str: """Format a schedule for table display with colors.""" # Color-coded status formatting status_enum = ScheduleStatus.from_raw(schedule.Status) if status_enum == ScheduleStatus.PENDING: status_display = f"{TableColors.WARNING}⏳ Pending{TableColors.RESET}" elif status_enum == ScheduleStatus.ACCEPTED: status_display = f"{TableColors.SUCCESS}✅ Accepted{TableColors.RESET}" elif status_enum == ScheduleStatus.DECLINED: status_display = f"{TableColors.ERROR}❌ Declined{TableColors.RESET}" else: status_display = f"{TableColors.DIM}❓ Unknown{TableColors.RESET}" # Handle ScheduledAt - could be datetime object or string from DB if schedule.ScheduledAt: if isinstance(schedule.ScheduledAt, str): # If it's a string, try to parse and format it, or use as-is try: dt_obj = datetime.fromisoformat(schedule.ScheduledAt.replace('Z', '+00:00')) scheduled_date = dt_obj.strftime("%Y-%m-%d %H:%M") except (ValueError, AttributeError): scheduled_date = str(schedule.ScheduledAt) else: # If it's already a datetime object scheduled_date = schedule.ScheduledAt.strftime("%Y-%m-%d %H:%M") else: scheduled_date = f"{TableColors.DIM}N/A{TableColors.RESET}" member_display = member_name if member_name else f"{TableColors.DIM}Unknown{TableColors.RESET}" service_display = service_info if service_info else f"{TableColors.DIM}Unknown{TableColors.RESET}" return (f"{TableColors.CYAN}{schedule.ScheduleId:3d}{TableColors.RESET} {TableColors.BORDER}│{TableColors.RESET} " f"{status_display:<20} {TableColors.BORDER}│{TableColors.RESET} " f"{TableColors.BOLD}{member_display:<20}{TableColors.RESET} {TableColors.BORDER}│{TableColors.RESET} " f"{TableColors.BLUE}{service_display:<20}{TableColors.RESET} {TableColors.BORDER}│{TableColors.RESET} {scheduled_date}") def create_table_header(*columns) -> str: """Create a styled table header.""" header_parts = [] for i, column in enumerate(columns): if i == 0: header_parts.append(f"{TableColors.HEADER}{column}{TableColors.RESET}") else: header_parts.append(f" {TableColors.BORDER}│{TableColors.RESET} {TableColors.HEADER}{column}{TableColors.RESET}") return "".join(header_parts) def create_table_separator(total_width: int) -> str: """Create a styled table separator line.""" return f"{TableColors.BORDER}{'─' * total_width}{TableColors.RESET}" def format_service_row(service, type_name: str, counts: dict) -> str: """Format a service for table display with colors.""" # Format date properly date_str = str(service.ServiceDate) if service.ServiceDate else f"{TableColors.DIM}N/A{TableColors.RESET}" # Color-code the counts total_display = f"{TableColors.BOLD}{counts['total']}{TableColors.RESET}" if counts['total'] > 0 else f"{TableColors.DIM}0{TableColors.RESET}" pending_display = f"{TableColors.WARNING}{counts['pending']}{TableColors.RESET}" if counts['pending'] > 0 else f"{TableColors.DIM}0{TableColors.RESET}" accepted_display = f"{TableColors.SUCCESS}{counts['accepted']}{TableColors.RESET}" if counts['accepted'] > 0 else f"{TableColors.DIM}0{TableColors.RESET}" declined_display = f"{TableColors.ERROR}{counts['declined']}{TableColors.RESET}" if counts['declined'] > 0 else f"{TableColors.DIM}0{TableColors.RESET}" return (f"{TableColors.CYAN}{service.ServiceId:<3}{TableColors.RESET} {TableColors.BORDER}│{TableColors.RESET} " f"{TableColors.BOLD}{date_str:<12}{TableColors.RESET} {TableColors.BORDER}│{TableColors.RESET} " f"{TableColors.YELLOW}{type_name:<12}{TableColors.RESET} {TableColors.BORDER}│{TableColors.RESET} " f"{total_display:<5} {TableColors.BORDER}│{TableColors.RESET} " f"{pending_display:<7} {TableColors.BORDER}│{TableColors.RESET} " f"{accepted_display:<8} {TableColors.BORDER}│{TableColors.RESET} {declined_display}")