feat(backend): design the cli to look professional
This commit is contained in:
@@ -8,13 +8,11 @@ from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from backend.cli.base import NimbusFlowCLI
|
||||
|
||||
from backend.cli.utils import format_member_row
|
||||
from backend.cli.utils import format_member_row, create_table_header, create_table_separator, TableColors
|
||||
|
||||
|
||||
def cmd_members_list(cli: "NimbusFlowCLI", args) -> None:
|
||||
"""List all members with optional filters."""
|
||||
print("Listing members...")
|
||||
|
||||
# Get all classifications for lookup
|
||||
classifications = cli.classification_repo.list_all()
|
||||
classification_map = {c.ClassificationId: c.ClassificationName for c in classifications}
|
||||
@@ -29,7 +27,7 @@ def cmd_members_list(cli: "NimbusFlowCLI", args) -> None:
|
||||
break
|
||||
|
||||
if classification_id is None:
|
||||
print(f"❌ Classification '{args.classification}' not found")
|
||||
print(f"{TableColors.ERROR}Classification '{args.classification}' not found{TableColors.RESET}")
|
||||
return
|
||||
|
||||
members = cli.member_repo.get_by_classification_ids([classification_id])
|
||||
@@ -39,26 +37,28 @@ def cmd_members_list(cli: "NimbusFlowCLI", args) -> None:
|
||||
members = cli.member_repo.list_all()
|
||||
|
||||
if not members:
|
||||
print("No members found.")
|
||||
print(f"{TableColors.DIM}No members found.{TableColors.RESET}")
|
||||
return
|
||||
|
||||
# Print header
|
||||
print(f"\n{'ID':<3} | {'First Name':<12} | {'Last Name':<15} | {'Classification':<12} | {'Active':<6} | {'Email'}")
|
||||
print("-" * 80)
|
||||
# Print styled header
|
||||
print()
|
||||
print(create_table_header("ID ", "First Name ", "Last Name ", "Classification ", "Status ", "Email"))
|
||||
print(create_table_separator(90))
|
||||
|
||||
# Print members
|
||||
for member in members:
|
||||
classification_name = classification_map.get(member.ClassificationId)
|
||||
print(format_member_row(member, classification_name))
|
||||
|
||||
print(f"\nTotal: {len(members)} members")
|
||||
print()
|
||||
print(f"{TableColors.SUCCESS}Total: {len(members)} members{TableColors.RESET}")
|
||||
|
||||
|
||||
def cmd_members_show(cli: "NimbusFlowCLI", args) -> None:
|
||||
"""Show detailed information about a specific member."""
|
||||
member = cli.member_repo.get_by_id(args.member_id)
|
||||
if not member:
|
||||
print(f"❌ Member with ID {args.member_id} not found")
|
||||
print(f"{TableColors.ERROR}Member with ID {args.member_id} not found{TableColors.RESET}")
|
||||
return
|
||||
|
||||
# Get classification name
|
||||
@@ -66,20 +66,24 @@ def cmd_members_show(cli: "NimbusFlowCLI", args) -> None:
|
||||
if member.ClassificationId:
|
||||
classification = cli.classification_repo.get_by_id(member.ClassificationId)
|
||||
|
||||
print(f"\n📋 Member Details (ID: {member.MemberId})")
|
||||
print("-" * 50)
|
||||
print(f"Name: {member.FirstName} {member.LastName}")
|
||||
print(f"Email: {member.Email or 'N/A'}")
|
||||
print(f"Phone: {member.PhoneNumber or 'N/A'}")
|
||||
print(f"Classification: {classification.ClassificationName if classification else 'N/A'}")
|
||||
print(f"Active: {'Yes' if member.IsActive else 'No'}")
|
||||
print(f"Notes: {member.Notes or 'N/A'}")
|
||||
print(f"\n{TableColors.HEADER}Member Details (ID: {member.MemberId}){TableColors.RESET}")
|
||||
print(f"{TableColors.BORDER}{'─' * 50}{TableColors.RESET}")
|
||||
print(f"{TableColors.BOLD}Name:{TableColors.RESET} {member.FirstName} {member.LastName}")
|
||||
print(f"{TableColors.BOLD}Email:{TableColors.RESET} {member.Email or f'{TableColors.DIM}N/A{TableColors.RESET}'}")
|
||||
print(f"{TableColors.BOLD}Phone:{TableColors.RESET} {member.PhoneNumber or f'{TableColors.DIM}N/A{TableColors.RESET}'}")
|
||||
print(f"{TableColors.BOLD}Classification:{TableColors.RESET} {TableColors.YELLOW}{classification.ClassificationName if classification else f'{TableColors.DIM}N/A'}{TableColors.RESET}")
|
||||
|
||||
print(f"\n⏰ Schedule History:")
|
||||
print(f"Last Scheduled: {member.LastScheduledAt or 'Never'}")
|
||||
print(f"Last Accepted: {member.LastAcceptedAt or 'Never'}")
|
||||
print(f"Last Declined: {member.LastDeclinedAt or 'Never'}")
|
||||
print(f"Decline Streak: {member.DeclineStreak}")
|
||||
active_status = f"{TableColors.SUCCESS}Yes{TableColors.RESET}" if member.IsActive else f"{TableColors.ERROR}No{TableColors.RESET}"
|
||||
print(f"{TableColors.BOLD}Active:{TableColors.RESET} {active_status}")
|
||||
print(f"{TableColors.BOLD}Notes:{TableColors.RESET} {member.Notes or f'{TableColors.DIM}N/A{TableColors.RESET}'}")
|
||||
|
||||
print(f"\n{TableColors.HEADER}Schedule History:{TableColors.RESET}")
|
||||
print(f"{TableColors.BOLD}Last Scheduled:{TableColors.RESET} {member.LastScheduledAt or f'{TableColors.DIM}Never{TableColors.RESET}'}")
|
||||
print(f"{TableColors.BOLD}Last Accepted:{TableColors.RESET} {member.LastAcceptedAt or f'{TableColors.DIM}Never{TableColors.RESET}'}")
|
||||
print(f"{TableColors.BOLD}Last Declined:{TableColors.RESET} {member.LastDeclinedAt or f'{TableColors.DIM}Never{TableColors.RESET}'}")
|
||||
|
||||
decline_color = TableColors.ERROR if member.DeclineStreak > 0 else TableColors.SUCCESS
|
||||
print(f"{TableColors.BOLD}Decline Streak:{TableColors.RESET} {decline_color}{member.DeclineStreak}{TableColors.RESET}")
|
||||
|
||||
|
||||
def setup_members_parser(subparsers) -> None:
|
||||
|
||||
@@ -10,13 +10,11 @@ if TYPE_CHECKING:
|
||||
from backend.cli.base import NimbusFlowCLI
|
||||
|
||||
from backend.models.enums import ScheduleStatus
|
||||
from backend.cli.utils import format_schedule_row
|
||||
from backend.cli.utils import format_schedule_row, create_table_header, create_table_separator, TableColors
|
||||
|
||||
|
||||
def cmd_schedules_list(cli: "NimbusFlowCLI", args) -> None:
|
||||
"""List schedules with optional filters."""
|
||||
print("Listing schedules...")
|
||||
|
||||
schedules = cli.schedule_repo.list_all()
|
||||
|
||||
# Apply filters
|
||||
@@ -28,11 +26,11 @@ def cmd_schedules_list(cli: "NimbusFlowCLI", args) -> None:
|
||||
status_enum = ScheduleStatus.from_raw(args.status.lower())
|
||||
schedules = [s for s in schedules if s.Status == status_enum.value]
|
||||
except ValueError:
|
||||
print(f"❌ Invalid status '{args.status}'. Valid options: pending, accepted, declined")
|
||||
print(f"{TableColors.ERROR}Invalid status '{args.status}'. Valid options: pending, accepted, declined{TableColors.RESET}")
|
||||
return
|
||||
|
||||
if not schedules:
|
||||
print("No schedules found.")
|
||||
print(f"{TableColors.DIM}No schedules found.{TableColors.RESET}")
|
||||
return
|
||||
|
||||
# Get member and service info for display
|
||||
@@ -44,9 +42,10 @@ def cmd_schedules_list(cli: "NimbusFlowCLI", args) -> None:
|
||||
type_name = service_type_map.get(service.ServiceTypeId, "Unknown")
|
||||
service_map[service.ServiceId] = f"{type_name} {service.ServiceDate}"
|
||||
|
||||
# Print header
|
||||
print(f"\n{'ID':<3} | {'Status':<10} | {'Member':<20} | {'Service':<15} | {'Scheduled'}")
|
||||
print("-" * 80)
|
||||
# Print styled header
|
||||
print()
|
||||
print(create_table_header("ID ", "Status ", "Member ", "Service ", "Scheduled"))
|
||||
print(create_table_separator(95))
|
||||
|
||||
# Print schedules
|
||||
for schedule in schedules:
|
||||
@@ -54,14 +53,15 @@ def cmd_schedules_list(cli: "NimbusFlowCLI", args) -> None:
|
||||
service_info = service_map.get(schedule.ServiceId, f"ID:{schedule.ServiceId}")
|
||||
print(format_schedule_row(schedule, member_name, service_info))
|
||||
|
||||
print(f"\nTotal: {len(schedules)} schedules")
|
||||
print()
|
||||
print(f"{TableColors.SUCCESS}Total: {len(schedules)} schedules{TableColors.RESET}")
|
||||
|
||||
|
||||
def cmd_schedules_show(cli: "NimbusFlowCLI", args) -> None:
|
||||
"""Show detailed information about a specific schedule."""
|
||||
schedule = cli.schedule_repo.get_by_id(args.schedule_id)
|
||||
if not schedule:
|
||||
print(f"❌ Schedule with ID {args.schedule_id} not found")
|
||||
print(f"{TableColors.ERROR}Schedule with ID {args.schedule_id} not found{TableColors.RESET}")
|
||||
return
|
||||
|
||||
# Get related information
|
||||
@@ -71,17 +71,39 @@ def cmd_schedules_show(cli: "NimbusFlowCLI", args) -> None:
|
||||
if service:
|
||||
service_type = cli.service_type_repo.get_by_id(service.ServiceTypeId)
|
||||
|
||||
print(f"\n📅 Schedule Details (ID: {schedule.ScheduleId})")
|
||||
print("-" * 50)
|
||||
print(f"Member: {member.FirstName} {member.LastName} (ID: {member.MemberId})" if member else f"Member ID: {schedule.MemberId}")
|
||||
print(f"Service: {service_type.TypeName if service_type else 'Unknown'} on {service.ServiceDate if service else 'Unknown'}")
|
||||
print(f"Status: {schedule.Status.upper()}")
|
||||
print(f"Scheduled At: {schedule.ScheduledAt}")
|
||||
print(f"Accepted At: {schedule.AcceptedAt or 'N/A'}")
|
||||
print(f"Declined At: {schedule.DeclinedAt or 'N/A'}")
|
||||
print(f"Expires At: {schedule.ExpiresAt or 'N/A'}")
|
||||
print(f"\n{TableColors.HEADER}Schedule Details (ID: {schedule.ScheduleId}){TableColors.RESET}")
|
||||
print(f"{TableColors.BORDER}{'─' * 50}{TableColors.RESET}")
|
||||
|
||||
# Member info with color
|
||||
if member:
|
||||
print(f"{TableColors.BOLD}Member:{TableColors.RESET} {TableColors.BOLD}{member.FirstName} {member.LastName}{TableColors.RESET} {TableColors.DIM}(ID: {member.MemberId}){TableColors.RESET}")
|
||||
else:
|
||||
print(f"{TableColors.BOLD}Member:{TableColors.RESET} {TableColors.DIM}Member ID: {schedule.MemberId}{TableColors.RESET}")
|
||||
|
||||
# Service info with color
|
||||
if service_type and service:
|
||||
print(f"{TableColors.BOLD}Service:{TableColors.RESET} {TableColors.YELLOW}{service_type.TypeName}{TableColors.RESET} on {TableColors.BLUE}{service.ServiceDate}{TableColors.RESET}")
|
||||
else:
|
||||
print(f"{TableColors.BOLD}Service:{TableColors.RESET} {TableColors.DIM}Unknown{TableColors.RESET}")
|
||||
|
||||
# Status with appropriate color
|
||||
status_enum = ScheduleStatus.from_raw(schedule.Status)
|
||||
if status_enum == ScheduleStatus.PENDING:
|
||||
status_color = TableColors.WARNING
|
||||
elif status_enum == ScheduleStatus.ACCEPTED:
|
||||
status_color = TableColors.SUCCESS
|
||||
elif status_enum == ScheduleStatus.DECLINED:
|
||||
status_color = TableColors.ERROR
|
||||
else:
|
||||
status_color = TableColors.DIM
|
||||
|
||||
print(f"{TableColors.BOLD}Status:{TableColors.RESET} {status_color}{schedule.Status.upper()}{TableColors.RESET}")
|
||||
print(f"{TableColors.BOLD}Scheduled At:{TableColors.RESET} {schedule.ScheduledAt or f'{TableColors.DIM}N/A{TableColors.RESET}'}")
|
||||
print(f"{TableColors.BOLD}Accepted At:{TableColors.RESET} {schedule.AcceptedAt or f'{TableColors.DIM}N/A{TableColors.RESET}'}")
|
||||
print(f"{TableColors.BOLD}Declined At:{TableColors.RESET} {schedule.DeclinedAt or f'{TableColors.DIM}N/A{TableColors.RESET}'}")
|
||||
print(f"{TableColors.BOLD}Expires At:{TableColors.RESET} {schedule.ExpiresAt or f'{TableColors.DIM}N/A{TableColors.RESET}'}")
|
||||
if schedule.DeclineReason:
|
||||
print(f"Decline Reason: {schedule.DeclineReason}")
|
||||
print(f"{TableColors.BOLD}Decline Reason:{TableColors.RESET} {TableColors.ERROR}{schedule.DeclineReason}{TableColors.RESET}")
|
||||
|
||||
|
||||
def cmd_schedules_accept(cli: "NimbusFlowCLI", args) -> None:
|
||||
|
||||
@@ -10,12 +10,11 @@ if TYPE_CHECKING:
|
||||
from backend.cli.base import NimbusFlowCLI
|
||||
|
||||
from backend.models.enums import ScheduleStatus
|
||||
from backend.cli.utils import format_service_row, create_table_header, create_table_separator, TableColors
|
||||
|
||||
|
||||
def cmd_services_list(cli: "NimbusFlowCLI", args) -> None:
|
||||
"""List services with optional filters."""
|
||||
print("Listing services...")
|
||||
|
||||
# Apply filters
|
||||
if args.upcoming:
|
||||
services = cli.service_repo.upcoming(limit=args.limit or 50)
|
||||
@@ -26,7 +25,7 @@ def cmd_services_list(cli: "NimbusFlowCLI", args) -> None:
|
||||
all_services = cli.service_repo.list_all()
|
||||
services = [s for s in all_services if s.ServiceDate == target_date]
|
||||
except ValueError:
|
||||
print(f"❌ Invalid date format '{args.date}'. Use YYYY-MM-DD")
|
||||
print(f"{TableColors.ERROR}Invalid date format '{args.date}'. Use YYYY-MM-DD{TableColors.RESET}")
|
||||
return
|
||||
else:
|
||||
services = cli.service_repo.list_all()
|
||||
@@ -34,7 +33,7 @@ def cmd_services_list(cli: "NimbusFlowCLI", args) -> None:
|
||||
services = services[:args.limit]
|
||||
|
||||
if not services:
|
||||
print("No services found.")
|
||||
print(f"{TableColors.DIM}No services found.{TableColors.RESET}")
|
||||
return
|
||||
|
||||
# Get service type names for display
|
||||
@@ -57,22 +56,19 @@ def cmd_services_list(cli: "NimbusFlowCLI", args) -> None:
|
||||
'total': len(service_schedules)
|
||||
}
|
||||
|
||||
# Print header
|
||||
print(f"\n{'ID':<3} | {'Date':<12} | {'Service Type':<12} | {'Total':<5} | {'Pending':<7} | {'Accepted':<8} | {'Declined'}")
|
||||
print("-" * 85)
|
||||
# Print styled header
|
||||
print()
|
||||
print(create_table_header("ID ", "Date ", "Service Type ", "Total", "Pending", "Accepted", "Declined"))
|
||||
print(create_table_separator(85))
|
||||
|
||||
# Print services
|
||||
for service in sorted(services, key=lambda s: s.ServiceDate):
|
||||
type_name = service_type_map.get(service.ServiceTypeId, "Unknown")
|
||||
counts = schedule_counts.get(service.ServiceId, {'total': 0, 'pending': 0, 'accepted': 0, 'declined': 0})
|
||||
|
||||
# Format date properly
|
||||
date_str = str(service.ServiceDate) if service.ServiceDate else "N/A"
|
||||
|
||||
print(f"{service.ServiceId:<3} | {date_str:<12} | {type_name:<12} | "
|
||||
f"{counts['total']:<5} | {counts['pending']:<7} | {counts['accepted']:<8} | {counts['declined']}")
|
||||
print(format_service_row(service, type_name, counts))
|
||||
|
||||
print(f"\nTotal: {len(services)} services")
|
||||
print()
|
||||
print(f"{TableColors.SUCCESS}Total: {len(services)} services{TableColors.RESET}")
|
||||
|
||||
|
||||
def setup_services_parser(subparsers) -> None:
|
||||
|
||||
@@ -15,46 +15,71 @@ from .commands import (
|
||||
cmd_services_list,
|
||||
)
|
||||
|
||||
# ANSI color codes
|
||||
class Colors:
|
||||
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'
|
||||
BG_GREY = '\033[100m'
|
||||
|
||||
# Special combinations
|
||||
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
|
||||
INPUT_BOX = '\033[90m' # Grey
|
||||
|
||||
|
||||
def create_input_box(prompt: str, width: int = 60) -> str:
|
||||
"""Create a grey box around input prompt."""
|
||||
box_top = f"{Colors.INPUT_BOX}┌" + "─" * (width - 2) + f"┐{Colors.RESET}"
|
||||
prompt_line = f"{Colors.INPUT_BOX}│{Colors.RESET} {prompt}"
|
||||
padding = width - len(prompt) - 3
|
||||
if padding > 0:
|
||||
prompt_line += " " * padding + f"{Colors.INPUT_BOX}│{Colors.RESET}"
|
||||
else:
|
||||
prompt_line += f" {Colors.INPUT_BOX}│{Colors.RESET}"
|
||||
box_bottom = f"{Colors.INPUT_BOX}└" + "─" * (width - 2) + f"┘{Colors.RESET}"
|
||||
|
||||
return f"\n{box_top}\n{prompt_line}\n{box_bottom}"
|
||||
|
||||
|
||||
def create_simple_input_box(prompt: str) -> str:
|
||||
"""Create a simple grey box around input prompt."""
|
||||
return f"\n{Colors.INPUT_BOX}┌─ {prompt} ─┐{Colors.RESET}"
|
||||
|
||||
|
||||
def clear_screen():
|
||||
"""Clear the terminal screen."""
|
||||
print("\033[2J\033[H")
|
||||
|
||||
|
||||
def load_ascii_art() -> str:
|
||||
"""Load ASCII art from file."""
|
||||
ascii_file = Path(__file__).parent / "ascii.txt"
|
||||
if ascii_file.exists():
|
||||
with open(ascii_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
# Use only the cloud portion (middle section) for better display
|
||||
lines = content.strip().split('\n')
|
||||
if len(lines) > 30:
|
||||
# Take a smaller portion of the cloud art
|
||||
start = len(lines) // 3
|
||||
end = start + 15
|
||||
return '\n'.join(lines[start:end])
|
||||
return content
|
||||
return "🎵 NimbusFlow 🎵" # Fallback if file doesn't exist
|
||||
|
||||
|
||||
def display_welcome():
|
||||
"""Display welcome screen with ASCII art."""
|
||||
"""Display welcome screen."""
|
||||
print("\033[2J\033[H") # Clear screen and move cursor to top
|
||||
|
||||
# Display the cloud ASCII art
|
||||
ascii_art = load_ascii_art()
|
||||
print(ascii_art)
|
||||
print()
|
||||
|
||||
# Add the NimbusFlow branding
|
||||
# NimbusFlow branding
|
||||
welcome_text = """
|
||||
╔═══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ ███╗ ██╗██╗███╗ ███╗██████╗ ██╗ ██╗███████╗ ███████╗██╗ ██████╗ ██╗ ██╗ ║
|
||||
║ ████╗ ██║██║████╗ ████║██╔══██╗██║ ██║██╔════╝ ██╔════╝██║ ██╔═══██╗██║ ██║ ║
|
||||
║ ██╔██╗ ██║██║██╔████╔██║██████╔╝██║ ██║███████╗ █████╗ ██║ ██║ ██║██║ █╗ ██║ ║
|
||||
║ ██║╚██╗██║██║██║╚██╔╝██║██╔══██╗██║ ██║╚════██║ ██╔══╝ ██║ ██║ ██║██║███╗██║ ║
|
||||
║ ██║ ╚████║██║██║ ╚═╝ ██║██████╔╝╚██████╔╝███████║ ██║ ███████╗╚██████╔╝╚███╔███╔╝ ║
|
||||
║ ╚═╝ ╚═══╝╚═╝╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚══════╝ ╚═════╝ ╚══╝╚══╝ ║
|
||||
║ ║
|
||||
║ 🎵 Choir Scheduling System 🎵 ║
|
||||
╚═══════════════════════════════════════════════════════════════════════════════╝
|
||||
╔════════════════════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ ███╗ ██╗██╗███╗ ███╗██████╗ ██╗ ██╗███████╗ ███████╗██╗ ██████╗ ██╗ ██╗ ║
|
||||
║ ████╗ ██║██║████╗ ████║██╔══██╗██║ ██║██╔════╝ ██╔════╝██║ ██╔═══██╗██║ ██║ ║
|
||||
║ ██╔██╗ ██║██║██╔████╔██║██████╔╝██║ ██║███████╗ █████╗ ██║ ██║ ██║██║ █╗ ██║ ║
|
||||
║ ██║╚██╗██║██║██║╚██╔╝██║██╔══██╗██║ ██║╚════██║ ██╔══╝ ██║ ██║ ██║██║███╗██║ ║
|
||||
║ ██║ ╚████║██║██║ ╚═╝ ██║██████╔╝╚██████╔╝███████║ ██║ ███████╗╚██████╔╝╚███╔███╔╝ ║
|
||||
║ ╚═╝ ╚═══╝╚═╝╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚══════╝ ╚═════╝ ╚══╝╚══╝ ║
|
||||
║ ║
|
||||
║ 🎵 Scheduling System 🎵 ║
|
||||
╚════════════════════════════════════════════════════════════════════════════════════════════╝
|
||||
"""
|
||||
print(welcome_text)
|
||||
print()
|
||||
@@ -62,59 +87,55 @@ def display_welcome():
|
||||
|
||||
def display_main_menu():
|
||||
"""Display the main menu options."""
|
||||
print("🎯 " + "="*60)
|
||||
print(" MAIN MENU - What would you like to manage?")
|
||||
print("🎯 " + "="*60)
|
||||
print(f"{Colors.HEADER}Main Menu{Colors.RESET}")
|
||||
print(f"{Colors.GREY}─" * 50 + f"{Colors.RESET}")
|
||||
print()
|
||||
print(" 1️⃣ 👥 Members - Manage choir members")
|
||||
print(" 2️⃣ 📅 Schedules - View and manage schedules")
|
||||
print(" 3️⃣ 🎼 Services - Manage services and events")
|
||||
print(" 4️⃣ ❌ Exit - Close NimbusFlow CLI")
|
||||
print(f" {Colors.CYAN}1.{Colors.RESET} {Colors.BOLD}Members{Colors.RESET} Manage choir members")
|
||||
print(f" {Colors.CYAN}2.{Colors.RESET} {Colors.BOLD}Schedules{Colors.RESET} View and manage schedules")
|
||||
print(f" {Colors.CYAN}3.{Colors.RESET} {Colors.BOLD}Services{Colors.RESET} Manage services and events")
|
||||
print(f" {Colors.CYAN}4.{Colors.RESET} {Colors.BOLD}Exit{Colors.RESET} Close NimbusFlow CLI")
|
||||
print()
|
||||
|
||||
|
||||
def display_members_menu():
|
||||
"""Display members submenu."""
|
||||
print("\n👥 " + "="*50)
|
||||
print(" MEMBERS MENU")
|
||||
print("👥 " + "="*50)
|
||||
print(f"\n{Colors.HEADER}Members{Colors.RESET}")
|
||||
print(f"{Colors.GREY}─" * 50 + f"{Colors.RESET}")
|
||||
print()
|
||||
print(" 1️⃣ 📋 List all members")
|
||||
print(" 2️⃣ ✅ List active members only")
|
||||
print(" 3️⃣ 🎵 List by classification")
|
||||
print(" 4️⃣ 👤 Show member details")
|
||||
print(" 5️⃣ 🔙 Back to main menu")
|
||||
print(f" {Colors.CYAN}1.{Colors.RESET} List all members")
|
||||
print(f" {Colors.CYAN}2.{Colors.RESET} List active members only")
|
||||
print(f" {Colors.CYAN}3.{Colors.RESET} List by classification")
|
||||
print(f" {Colors.CYAN}4.{Colors.RESET} Show member details")
|
||||
print(f" {Colors.CYAN}5.{Colors.RESET} {Colors.DIM}Back to main menu{Colors.RESET}")
|
||||
print()
|
||||
|
||||
|
||||
def display_schedules_menu():
|
||||
"""Display schedules submenu."""
|
||||
print("\n📅 " + "="*50)
|
||||
print(" SCHEDULES MENU")
|
||||
print("📅 " + "="*50)
|
||||
print(f"\n{Colors.HEADER}Schedules{Colors.RESET}")
|
||||
print(f"{Colors.GREY}─" * 50 + f"{Colors.RESET}")
|
||||
print()
|
||||
print(" 1️⃣ 📋 List all schedules")
|
||||
print(" 2️⃣ ⏳ List pending schedules")
|
||||
print(" 3️⃣ ✅ List accepted schedules")
|
||||
print(" 4️⃣ ❌ List declined schedules")
|
||||
print(" 5️⃣ 👤 Show schedule details")
|
||||
print(" 6️⃣ ✨ Accept a schedule (interactive)")
|
||||
print(" 7️⃣ 🚫 Decline a schedule (interactive)")
|
||||
print(" 8️⃣ 📅 Schedule next member for service")
|
||||
print(" 9️⃣ 🔙 Back to main menu")
|
||||
print(f" {Colors.CYAN}1.{Colors.RESET} List all schedules")
|
||||
print(f" {Colors.CYAN}2.{Colors.RESET} List pending schedules")
|
||||
print(f" {Colors.CYAN}3.{Colors.RESET} List accepted schedules")
|
||||
print(f" {Colors.CYAN}4.{Colors.RESET} List declined schedules")
|
||||
print(f" {Colors.CYAN}5.{Colors.RESET} Show schedule details")
|
||||
print(f" {Colors.CYAN}6.{Colors.RESET} {Colors.GREEN}Accept a schedule{Colors.RESET}")
|
||||
print(f" {Colors.CYAN}7.{Colors.RESET} {Colors.RED}Decline a schedule{Colors.RESET}")
|
||||
print(f" {Colors.CYAN}8.{Colors.RESET} {Colors.YELLOW}Schedule next member for service{Colors.RESET}")
|
||||
print(f" {Colors.CYAN}9.{Colors.RESET} {Colors.DIM}Back to main menu{Colors.RESET}")
|
||||
print()
|
||||
|
||||
|
||||
def display_services_menu():
|
||||
"""Display services submenu."""
|
||||
print("\n🎼 " + "="*50)
|
||||
print(" SERVICES MENU")
|
||||
print("🎼 " + "="*50)
|
||||
print(f"\n{Colors.HEADER}Services{Colors.RESET}")
|
||||
print(f"{Colors.GREY}─" * 50 + f"{Colors.RESET}")
|
||||
print()
|
||||
print(" 1️⃣ 📋 List all services")
|
||||
print(" 2️⃣ 🔮 List upcoming services")
|
||||
print(" 3️⃣ 📅 List services by date")
|
||||
print(" 4️⃣ 🔙 Back to main menu")
|
||||
print(f" {Colors.CYAN}1.{Colors.RESET} List all services")
|
||||
print(f" {Colors.CYAN}2.{Colors.RESET} List upcoming services")
|
||||
print(f" {Colors.CYAN}3.{Colors.RESET} List services by date")
|
||||
print(f" {Colors.CYAN}4.{Colors.RESET} {Colors.DIM}Back to main menu{Colors.RESET}")
|
||||
print()
|
||||
|
||||
|
||||
@@ -122,18 +143,19 @@ def get_user_choice(max_options: int) -> int:
|
||||
"""Get user menu choice with validation."""
|
||||
while True:
|
||||
try:
|
||||
choice = input(f"🎯 Enter your choice (1-{max_options}): ").strip()
|
||||
print(create_simple_input_box(f"Enter your choice (1-{max_options})"))
|
||||
choice = input(f"{Colors.INPUT_BOX}└─> {Colors.RESET}").strip()
|
||||
if not choice:
|
||||
continue
|
||||
choice_int = int(choice)
|
||||
if 1 <= choice_int <= max_options:
|
||||
return choice_int
|
||||
else:
|
||||
print(f"❌ Please enter a number between 1 and {max_options}")
|
||||
print(f"{Colors.ERROR}Please enter a number between 1 and {max_options}{Colors.RESET}")
|
||||
except ValueError:
|
||||
print("❌ Please enter a valid number")
|
||||
print(f"{Colors.ERROR}Please enter a valid number{Colors.RESET}")
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
print("\n🛑 Goodbye!")
|
||||
print(f"\n{Colors.WARNING}Goodbye!{Colors.RESET}")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
@@ -141,12 +163,13 @@ def get_text_input(prompt: str, required: bool = True) -> str:
|
||||
"""Get text input from user."""
|
||||
while True:
|
||||
try:
|
||||
value = input(f"📝 {prompt}: ").strip()
|
||||
print(create_simple_input_box(prompt))
|
||||
value = input(f"{Colors.INPUT_BOX}└─> {Colors.RESET}").strip()
|
||||
if value or not required:
|
||||
return value
|
||||
print("❌ This field is required")
|
||||
print(f"{Colors.ERROR}This field is required{Colors.RESET}")
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
print("\n🛑 Operation cancelled")
|
||||
print(f"\n{Colors.WARNING}Operation cancelled{Colors.RESET}")
|
||||
return ""
|
||||
|
||||
|
||||
@@ -154,18 +177,19 @@ def get_date_input(prompt: str = "Enter date (YYYY-MM-DD)") -> str:
|
||||
"""Get date input from user."""
|
||||
while True:
|
||||
try:
|
||||
date_str = input(f"📅 {prompt}: ").strip()
|
||||
print(create_simple_input_box(prompt))
|
||||
date_str = input(f"{Colors.INPUT_BOX}└─> {Colors.RESET}").strip()
|
||||
if not date_str:
|
||||
print("❌ Date is required")
|
||||
print(f"{Colors.ERROR}Date is required{Colors.RESET}")
|
||||
continue
|
||||
# Basic date format validation
|
||||
if len(date_str) == 10 and date_str.count('-') == 2:
|
||||
parts = date_str.split('-')
|
||||
if len(parts[0]) == 4 and len(parts[1]) == 2 and len(parts[2]) == 2:
|
||||
return date_str
|
||||
print("❌ Please use format YYYY-MM-DD (e.g., 2025-09-07)")
|
||||
print(f"{Colors.ERROR}Please use format YYYY-MM-DD (e.g., 2025-09-07){Colors.RESET}")
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
print("\n🛑 Operation cancelled")
|
||||
print(f"\n{Colors.WARNING}Operation cancelled{Colors.RESET}")
|
||||
return ""
|
||||
|
||||
|
||||
@@ -179,34 +203,41 @@ class MockArgs:
|
||||
def handle_members_menu(cli: "NimbusFlowCLI"):
|
||||
"""Handle members menu interactions."""
|
||||
while True:
|
||||
clear_screen()
|
||||
display_members_menu()
|
||||
choice = get_user_choice(5)
|
||||
|
||||
if choice == 1: # List all members
|
||||
print("\n🔍 Listing all members...")
|
||||
clear_screen()
|
||||
print(f"{Colors.SUCCESS}Listing all members...{Colors.RESET}\n")
|
||||
cmd_members_list(cli, MockArgs(active=False, classification=None))
|
||||
input("\n⏸️ Press Enter to continue...")
|
||||
input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}")
|
||||
|
||||
elif choice == 2: # List active members
|
||||
print("\n🔍 Listing active members...")
|
||||
clear_screen()
|
||||
print(f"{Colors.SUCCESS}Listing active members...{Colors.RESET}\n")
|
||||
cmd_members_list(cli, MockArgs(active=True, classification=None))
|
||||
input("\n⏸️ Press Enter to continue...")
|
||||
input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}")
|
||||
|
||||
elif choice == 3: # List by classification
|
||||
clear_screen()
|
||||
classification = get_text_input("Enter classification (Soprano, Alto / Mezzo, Tenor, Baritone)", True)
|
||||
if classification:
|
||||
print(f"\n🔍 Listing {classification} members...")
|
||||
clear_screen()
|
||||
print(f"{Colors.SUCCESS}Listing {classification} members...{Colors.RESET}\n")
|
||||
cmd_members_list(cli, MockArgs(active=False, classification=classification))
|
||||
input("\n⏸️ Press Enter to continue...")
|
||||
input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}")
|
||||
|
||||
elif choice == 4: # Show member details
|
||||
clear_screen()
|
||||
member_id = get_text_input("Enter member ID", True)
|
||||
if member_id.isdigit():
|
||||
print(f"\n🔍 Showing details for member {member_id}...")
|
||||
clear_screen()
|
||||
print(f"{Colors.SUCCESS}Showing details for member {member_id}...{Colors.RESET}\n")
|
||||
cmd_members_show(cli, MockArgs(member_id=int(member_id)))
|
||||
else:
|
||||
print("❌ Invalid member ID")
|
||||
input("\n⏸️ Press Enter to continue...")
|
||||
print(f"{Colors.ERROR}Invalid member ID{Colors.RESET}")
|
||||
input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}")
|
||||
|
||||
elif choice == 5: # Back to main menu
|
||||
break
|
||||
@@ -215,59 +246,73 @@ def handle_members_menu(cli: "NimbusFlowCLI"):
|
||||
def handle_schedules_menu(cli: "NimbusFlowCLI"):
|
||||
"""Handle schedules menu interactions."""
|
||||
while True:
|
||||
clear_screen()
|
||||
display_schedules_menu()
|
||||
choice = get_user_choice(9)
|
||||
|
||||
if choice == 1: # List all schedules
|
||||
print("\n🔍 Listing all schedules...")
|
||||
clear_screen()
|
||||
print(f"{Colors.SUCCESS}Listing all schedules...{Colors.RESET}\n")
|
||||
cmd_schedules_list(cli, MockArgs(service_id=None, status=None))
|
||||
input("\n⏸️ Press Enter to continue...")
|
||||
input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}")
|
||||
|
||||
elif choice == 2: # List pending schedules
|
||||
print("\n🔍 Listing pending schedules...")
|
||||
clear_screen()
|
||||
print(f"{Colors.WARNING}Listing pending schedules...{Colors.RESET}\n")
|
||||
cmd_schedules_list(cli, MockArgs(service_id=None, status="pending"))
|
||||
input("\n⏸️ Press Enter to continue...")
|
||||
input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}")
|
||||
|
||||
elif choice == 3: # List accepted schedules
|
||||
print("\n🔍 Listing accepted schedules...")
|
||||
clear_screen()
|
||||
print(f"{Colors.SUCCESS}Listing accepted schedules...{Colors.RESET}\n")
|
||||
cmd_schedules_list(cli, MockArgs(service_id=None, status="accepted"))
|
||||
input("\n⏸️ Press Enter to continue...")
|
||||
input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}")
|
||||
|
||||
elif choice == 4: # List declined schedules
|
||||
print("\n🔍 Listing declined schedules...")
|
||||
clear_screen()
|
||||
print(f"{Colors.ERROR}Listing declined schedules...{Colors.RESET}\n")
|
||||
cmd_schedules_list(cli, MockArgs(service_id=None, status="declined"))
|
||||
input("\n⏸️ Press Enter to continue...")
|
||||
input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}")
|
||||
|
||||
elif choice == 5: # Show schedule details
|
||||
clear_screen()
|
||||
schedule_id = get_text_input("Enter schedule ID", True)
|
||||
if schedule_id.isdigit():
|
||||
print(f"\n🔍 Showing details for schedule {schedule_id}...")
|
||||
clear_screen()
|
||||
print(f"{Colors.SUCCESS}Showing details for schedule {schedule_id}...{Colors.RESET}\n")
|
||||
cmd_schedules_show(cli, MockArgs(schedule_id=int(schedule_id)))
|
||||
else:
|
||||
print("❌ Invalid schedule ID")
|
||||
input("\n⏸️ Press Enter to continue...")
|
||||
print(f"{Colors.ERROR}Invalid schedule ID{Colors.RESET}")
|
||||
input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}")
|
||||
|
||||
elif choice == 6: # Accept schedule
|
||||
clear_screen()
|
||||
date = get_date_input("Enter date for interactive accept")
|
||||
if date:
|
||||
print(f"\n✨ Starting interactive accept for {date}...")
|
||||
clear_screen()
|
||||
print(f"{Colors.SUCCESS}Starting interactive accept for {date}...{Colors.RESET}\n")
|
||||
cmd_schedules_accept(cli, MockArgs(date=date, schedule_id=None))
|
||||
input("\n⏸️ Press Enter to continue...")
|
||||
input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}")
|
||||
|
||||
elif choice == 7: # Decline schedule
|
||||
clear_screen()
|
||||
date = get_date_input("Enter date for interactive decline")
|
||||
if date:
|
||||
clear_screen()
|
||||
reason = get_text_input("Enter decline reason (optional)", False)
|
||||
print(f"\n🚫 Starting interactive decline for {date}...")
|
||||
clear_screen()
|
||||
print(f"{Colors.ERROR}Starting interactive decline for {date}...{Colors.RESET}\n")
|
||||
cmd_schedules_decline(cli, MockArgs(date=date, schedule_id=None, reason=reason))
|
||||
input("\n⏸️ Press Enter to continue...")
|
||||
input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}")
|
||||
|
||||
elif choice == 8: # Schedule next member
|
||||
clear_screen()
|
||||
date = get_date_input("Enter date to schedule for")
|
||||
if date:
|
||||
print(f"\n📅 Starting scheduling for {date}...")
|
||||
clear_screen()
|
||||
print(f"{Colors.WARNING}Starting scheduling for {date}...{Colors.RESET}\n")
|
||||
cmd_schedules_schedule(cli, MockArgs(service_id=None, date=date, classifications=None))
|
||||
input("\n⏸️ Press Enter to continue...")
|
||||
input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}")
|
||||
|
||||
elif choice == 9: # Back to main menu
|
||||
break
|
||||
@@ -276,25 +321,30 @@ def handle_schedules_menu(cli: "NimbusFlowCLI"):
|
||||
def handle_services_menu(cli: "NimbusFlowCLI"):
|
||||
"""Handle services menu interactions."""
|
||||
while True:
|
||||
clear_screen()
|
||||
display_services_menu()
|
||||
choice = get_user_choice(4)
|
||||
|
||||
if choice == 1: # List all services
|
||||
print("\n🔍 Listing all services...")
|
||||
clear_screen()
|
||||
print(f"{Colors.SUCCESS}Listing all services...{Colors.RESET}\n")
|
||||
cmd_services_list(cli, MockArgs(date=None, upcoming=False, limit=None))
|
||||
input("\n⏸️ Press Enter to continue...")
|
||||
input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}")
|
||||
|
||||
elif choice == 2: # List upcoming services
|
||||
print("\n🔍 Listing upcoming services...")
|
||||
clear_screen()
|
||||
print(f"{Colors.WARNING}Listing upcoming services...{Colors.RESET}\n")
|
||||
cmd_services_list(cli, MockArgs(date=None, upcoming=True, limit=20))
|
||||
input("\n⏸️ Press Enter to continue...")
|
||||
input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}")
|
||||
|
||||
elif choice == 3: # List by date
|
||||
clear_screen()
|
||||
date = get_date_input("Enter date to filter services")
|
||||
if date:
|
||||
print(f"\n🔍 Listing services for {date}...")
|
||||
clear_screen()
|
||||
print(f"{Colors.SUCCESS}Listing services for {date}...{Colors.RESET}\n")
|
||||
cmd_services_list(cli, MockArgs(date=date, upcoming=False, limit=None))
|
||||
input("\n⏸️ Press Enter to continue...")
|
||||
input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}")
|
||||
|
||||
elif choice == 4: # Back to main menu
|
||||
break
|
||||
@@ -304,13 +354,13 @@ def run_interactive_mode(cli: "NimbusFlowCLI"):
|
||||
"""Run the main interactive CLI mode."""
|
||||
display_welcome()
|
||||
|
||||
print("🎉 Welcome to the NimbusFlow Interactive CLI!")
|
||||
print(" Navigate through menus to manage your choir scheduling system.")
|
||||
print(f"{Colors.HEADER}Welcome to the NimbusFlow Interactive CLI{Colors.RESET}")
|
||||
print(f"{Colors.DIM}Navigate through menus to manage your choir scheduling system.{Colors.RESET}")
|
||||
print()
|
||||
input("⏸️ Press Enter to continue...")
|
||||
input(f"{Colors.DIM}Press Enter to continue...{Colors.RESET}")
|
||||
|
||||
while True:
|
||||
print("\033[2J\033[H") # Clear screen
|
||||
clear_screen() # Clear screen
|
||||
display_main_menu()
|
||||
|
||||
choice = get_user_choice(4)
|
||||
@@ -325,6 +375,7 @@ def run_interactive_mode(cli: "NimbusFlowCLI"):
|
||||
handle_services_menu(cli)
|
||||
|
||||
elif choice == 4: # Exit
|
||||
print("\n🎵 Thank you for using NimbusFlow!")
|
||||
print(" Have a wonderful day! 🌟")
|
||||
clear_screen()
|
||||
print(f"\n{Colors.SUCCESS}Thank you for using NimbusFlow!{Colors.RESET}")
|
||||
print(f"{Colors.DIM}Goodbye!{Colors.RESET}")
|
||||
break
|
||||
@@ -6,22 +6,59 @@ 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
|
||||
|
||||
|
||||
def format_member_row(member, classification_name: Optional[str] = None) -> str:
|
||||
"""Format a member for table display."""
|
||||
active = "✓" if member.IsActive else "✗"
|
||||
"""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"
|
||||
return f"{member.MemberId:3d} | {member.FirstName:<12} | {member.LastName:<15} | {classification:<12} | {active:^6} | {member.Email 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."""
|
||||
status_symbols = {
|
||||
ScheduleStatus.PENDING: "⏳",
|
||||
ScheduleStatus.ACCEPTED: "✅",
|
||||
ScheduleStatus.DECLINED: "❌"
|
||||
}
|
||||
status_symbol = status_symbols.get(ScheduleStatus.from_raw(schedule.Status), "❓")
|
||||
"""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:
|
||||
@@ -36,6 +73,50 @@ def format_schedule_row(schedule, member_name: str = "", service_info: str = "")
|
||||
# If it's already a datetime object
|
||||
scheduled_date = schedule.ScheduledAt.strftime("%Y-%m-%d %H:%M")
|
||||
else:
|
||||
scheduled_date = "N/A"
|
||||
scheduled_date = f"{TableColors.DIM}N/A{TableColors.RESET}"
|
||||
|
||||
return f"{schedule.ScheduleId:3d} | {status_symbol} {schedule.Status:<8} | {member_name:<20} | {service_info:<15} | {scheduled_date}"
|
||||
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}")
|
||||
Reference in New Issue
Block a user