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:
|
||||
|
||||
Reference in New Issue
Block a user