Implements full Google Sheets scorecard submission with: - Complete game data extraction (68 play fields, pitching decisions, box score) - Transaction rollback support at 3 states (plays/game/complete) - Duplicate game detection with confirmation dialog - Permission-based submission (GMs only) - Automated results posting to news channel - Automatic standings recalculation - Key plays display with WPA sorting New Components: - Play, Decision, Game models with full validation - SheetsService for Google Sheets integration - GameService, PlayService, DecisionService for data management - ConfirmationView for user confirmations - Discord helper utilities for channel operations Services Enhanced: - StandingsService: Added recalculate_standings() method - CustomCommandsService: Fixed creator endpoint path - Team/Player models: Added helper methods for display Configuration: - Added SHEETS_CREDENTIALS_PATH environment variable - Added SBA_NETWORK_NEWS_CHANNEL and role constants - Enabled pygsheets dependency Documentation: - Comprehensive README updates across all modules - Added command, service, model, and view documentation - Detailed workflow and error handling documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| __init__.py | ||
| base.py | ||
| common.py | ||
| confirmations.py | ||
| custom_commands.py | ||
| embeds.py | ||
| help_commands.py | ||
| modals.py | ||
| README.md | ||
| trade_embed.py | ||
| transaction_embed.py | ||
Views Directory
The views directory contains Discord UI components for Discord Bot v2.0, providing consistent visual interfaces and interactive elements. This includes embeds, modals, buttons, select menus, and other Discord UI components.
Architecture
Component-Based UI Design
Views in Discord Bot v2.0 follow these principles:
- Consistent styling via centralized templates
- Reusable components for common UI patterns
- Error handling with graceful degradation
- User interaction tracking and validation
- Accessibility with proper labeling and feedback
Base Components
All view components inherit from Discord.py base classes with enhanced functionality:
- BaseView - Enhanced discord.ui.View with logging and user validation
- BaseModal - Enhanced discord.ui.Modal with error handling
- EmbedTemplate - Centralized embed creation with consistent styling
View Components
Base View System (base.py)
BaseView Class
Foundation for all interactive views:
class BaseView(discord.ui.View):
def __init__(self, timeout=180.0, user_id=None):
super().__init__(timeout=timeout)
self.user_id = user_id
self.logger = get_contextual_logger(f'{__name__}.BaseView')
async def interaction_check(self, interaction) -> bool:
"""Validate user permissions for interaction."""
async def on_timeout(self) -> None:
"""Handle view timeout gracefully."""
async def on_error(self, interaction, error, item) -> None:
"""Handle view errors with user feedback."""
ConfirmationView Class (Updated January 2025)
Reusable confirmation dialog with Confirm/Cancel buttons (confirmations.py):
Key Features:
- User restriction: Only specified users can interact
- Customizable labels and styles: Flexible button appearance
- Timeout handling: Automatic cleanup after timeout
- Three-state result:
True(confirmed),False(cancelled),None(timeout) - Clean interface: Automatically removes buttons after interaction
Usage Pattern:
from views.confirmations import ConfirmationView
# Create confirmation dialog
view = ConfirmationView(
responders=[interaction.user], # Only this user can interact
timeout=30.0, # 30 second timeout
confirm_label="Yes, delete", # Custom label
cancel_label="No, keep it" # Custom label
)
# Send confirmation
await interaction.edit_original_response(
content="⚠️ Are you sure you want to delete this?",
view=view
)
# Wait for user response
await view.wait()
# Check result
if view.confirmed is True:
# User clicked Confirm
await interaction.edit_original_response(
content="✅ Deleted successfully",
view=None
)
elif view.confirmed is False:
# User clicked Cancel
await interaction.edit_original_response(
content="❌ Cancelled",
view=None
)
else:
# Timeout occurred (view.confirmed is None)
await interaction.edit_original_response(
content="⏱️ Request timed out",
view=None
)
Real-World Example (Scorecard Submission):
# From commands/league/submit_scorecard.py
if duplicate_game:
view = ConfirmationView(
responders=[interaction.user],
timeout=30.0
)
await interaction.edit_original_response(
content=(
f"⚠️ This game has already been played!\n"
f"Would you like me to wipe the old one and re-submit?"
),
view=view
)
await view.wait()
if view.confirmed:
# User confirmed - proceed with wipe and resubmit
await wipe_old_data()
else:
# User cancelled - exit gracefully
return
Configuration Options:
ConfirmationView(
responders=[user1, user2], # Multiple users allowed
timeout=60.0, # Custom timeout
confirm_label="Approve", # Custom confirm text
cancel_label="Reject", # Custom cancel text
confirm_style=discord.ButtonStyle.red, # Custom button style
cancel_style=discord.ButtonStyle.grey # Custom button style
)
PaginationView Class
Multi-page navigation for large datasets:
pages = [embed1, embed2, embed3]
pagination = PaginationView(
pages=pages,
user_id=interaction.user.id,
show_page_numbers=True
)
await interaction.followup.send(embed=pagination.get_current_embed(), view=pagination)
Embed Templates (embeds.py)
EmbedTemplate Class
Centralized embed creation with consistent styling:
# Success embed
embed = EmbedTemplate.success(
title="Operation Completed",
description="Your request was processed successfully."
)
# Error embed
embed = EmbedTemplate.error(
title="Operation Failed",
description="Please check your input and try again."
)
# Warning embed
embed = EmbedTemplate.warning(
title="Careful!",
description="This action cannot be undone."
)
# Info embed
embed = EmbedTemplate.info(
title="Information",
description="Here's what you need to know."
)
EmbedColors Dataclass
Consistent color scheme across all embeds:
@dataclass(frozen=True)
class EmbedColors:
PRIMARY: int = 0xa6ce39 # SBA green
SUCCESS: int = 0x28a745 # Green
WARNING: int = 0xffc107 # Yellow
ERROR: int = 0xdc3545 # Red
INFO: int = 0x17a2b8 # Blue
SECONDARY: int = 0x6c757d # Gray
Modal Forms (modals.py)
BaseModal Class
Foundation for interactive forms:
class BaseModal(discord.ui.Modal):
def __init__(self, title: str, timeout=300.0):
super().__init__(title=title, timeout=timeout)
self.logger = get_contextual_logger(f'{__name__}.BaseModal')
self.result = None
async def on_submit(self, interaction):
"""Handle form submission."""
async def on_error(self, interaction, error):
"""Handle form errors."""
Usage Pattern
class CustomCommandModal(BaseModal):
def __init__(self):
super().__init__(title="Create Custom Command")
name = discord.ui.TextInput(
label="Command Name",
placeholder="Enter command name...",
required=True,
max_length=50
)
response = discord.ui.TextInput(
label="Response",
placeholder="Enter command response...",
style=discord.TextStyle.paragraph,
required=True,
max_length=2000
)
async def on_submit(self, interaction):
# Process form data
command_data = {
"name": self.name.value,
"response": self.response.value
}
# Handle creation logic
Common UI Elements (common.py)
Shared Components
- Loading indicators for async operations
- Status messages for operation feedback
- Navigation elements for multi-step processes
- Validation displays for form errors
Specialized Views
Custom Commands (custom_commands.py)
Views specific to custom command management:
- Command creation forms
- Command listing with actions
- Bulk management interfaces
Transaction Management (transaction_embed.py)
Views for player transaction interfaces:
- Transaction builder with interactive controls
- Comprehensive validation and sWAR display
- Pre-existing transaction context
- Approval/submission workflows
Styling Guidelines
Embed Consistency
All embeds should use EmbedTemplate methods:
# ✅ Consistent styling
embed = EmbedTemplate.success("Player Added", "Player successfully added to roster")
# ❌ Inconsistent styling
embed = discord.Embed(title="Player Added", color=0x00ff00)
Color Usage
Use the standard color palette:
- PRIMARY (SBA Green) - Default for neutral information
- SUCCESS (Green) - Successful operations
- ERROR (Red) - Errors and failures
- WARNING (Yellow) - Warnings and cautions
- INFO (Blue) - General information
- SECONDARY (Gray) - Less important information
User Feedback
Provide clear feedback for all user interactions:
# Loading state
embed = EmbedTemplate.info("Processing", "Please wait while we process your request...")
# Success state
embed = EmbedTemplate.success("Complete", "Your request has been processed successfully.")
# Error state with helpful information
embed = EmbedTemplate.error(
"Request Failed",
"The player name was not found. Please check your spelling and try again."
)
Interactive Components
Button Patterns
Action Buttons
@discord.ui.button(label="Confirm", style=discord.ButtonStyle.success, emoji="✅")
async def confirm_button(self, interaction, button):
self.increment_interaction_count()
# Handle confirmation
await interaction.response.edit_message(content="Confirmed!", view=None)
Navigation Buttons
@discord.ui.button(emoji="◀️", style=discord.ButtonStyle.primary)
async def previous_page(self, interaction, button):
self.current_page = max(0, self.current_page - 1)
await interaction.response.edit_message(embed=self.get_current_embed(), view=self)
Select Menu Patterns
Option Selection
@discord.ui.select(placeholder="Choose an option...")
async def select_option(self, interaction, select):
selected_value = select.values[0]
# Handle selection
await interaction.response.send_message(f"You selected: {selected_value}")
Dynamic Options
class PlayerSelectMenu(discord.ui.Select):
def __init__(self, players: List[Player]):
options = [
discord.SelectOption(
label=player.name,
value=str(player.id),
description=f"{player.position} - {player.team.abbrev}"
)
for player in players[:25] # Discord limit
]
super().__init__(placeholder="Select a player...", options=options)
Error Handling
View Error Handling
All views implement comprehensive error handling:
async def on_error(self, interaction, error, item):
"""Handle view errors gracefully."""
self.logger.error("View error", error=error, item_type=type(item).__name__)
try:
embed = EmbedTemplate.error(
"Interaction Error",
"Something went wrong. Please try again."
)
if not interaction.response.is_done():
await interaction.response.send_message(embed=embed, ephemeral=True)
else:
await interaction.followup.send(embed=embed, ephemeral=True)
except Exception as e:
self.logger.error("Failed to send error message", error=e)
User Input Validation
Forms validate user input before processing:
async def on_submit(self, interaction):
# Validate input
if len(self.name.value) < 2:
embed = EmbedTemplate.error(
"Invalid Input",
"Command name must be at least 2 characters long."
)
await interaction.response.send_message(embed=embed, ephemeral=True)
return
# Process valid input
await self.create_command(interaction)
Accessibility Features
User-Friendly Labels
- Clear button labels with descriptive text
- Helpful placeholders in form fields
- Descriptive error messages with actionable guidance
- Consistent emoji usage for visual recognition
Permission Validation
Views respect user permissions and provide appropriate feedback:
async def interaction_check(self, interaction) -> bool:
"""Check if user can interact with this view."""
if self.user_id and interaction.user.id != self.user_id:
await interaction.response.send_message(
"❌ You cannot interact with this menu.",
ephemeral=True
)
return False
return True
Performance Considerations
View Lifecycle Management
- Timeout handling prevents orphaned views
- Resource cleanup in view destructors
- Interaction tracking for usage analytics
- Memory management for large datasets
Efficient Updates
# ✅ Efficient - Only update what changed
await interaction.response.edit_message(embed=new_embed, view=self)
# ❌ Inefficient - Sends new message
await interaction.response.send_message(embed=new_embed, view=new_view)
Testing Strategies
View Testing
@pytest.mark.asyncio
async def test_confirmation_view():
view = ConfirmationView(user_id=123)
# Mock interaction
interaction = Mock()
interaction.user.id = 123
# Test button click
await view.confirm_button.callback(interaction)
assert view.result is True
Modal Testing
@pytest.mark.asyncio
async def test_custom_command_modal():
modal = CustomCommandModal()
# Set form values
modal.name.value = "test"
modal.response.value = "Test response"
# Mock interaction
interaction = Mock()
# Test form submission
await modal.on_submit(interaction)
# Verify processing
assert modal.result is not None
Development Guidelines
Creating New Views
- Inherit from base classes for consistency
- Use EmbedTemplate for all embed creation
- Implement proper error handling in all interactions
- Add user permission checks where appropriate
- Include comprehensive logging with context
- Follow timeout patterns to prevent resource leaks
View Composition
- Keep views focused on single responsibilities
- Use composition over complex inheritance
- Separate business logic from UI logic
- Make views testable with dependency injection
UI Guidelines
- Follow Discord design patterns for familiarity
- Use consistent colors from EmbedColors
- Provide clear user feedback for all actions
- Handle edge cases gracefully
- Consider mobile users in layout design
Transaction Embed Enhancements (January 2025)
Enhanced Display Features
The transaction embed now provides comprehensive information for better decision-making:
New Embed Sections
async def create_transaction_embed(builder: TransactionBuilder) -> discord.Embed:
"""
Creates enhanced transaction embed with sWAR and pre-existing transaction context.
"""
# Existing sections...
# NEW: Team Cost (sWAR) Display
swar_status = f"{validation.major_league_swar_status}\n{validation.minor_league_swar_status}"
embed.add_field(name="Team sWAR", value=swar_status, inline=False)
# NEW: Pre-existing Transaction Context (when applicable)
if validation.pre_existing_transactions_note:
embed.add_field(
name="📋 Transaction Context",
value=validation.pre_existing_transactions_note,
inline=False
)
Enhanced Information Display
sWAR Tracking
- Major League sWAR: Projected team cost for ML roster
- Minor League sWAR: Projected team cost for MiL roster
- Formatted Display: Uses 📊 emoji with 1 decimal precision
Pre-existing Transaction Context
Dynamic context display based on scheduled moves:
# Example displays:
"ℹ️ **Pre-existing Moves**: 3 scheduled moves (+3.7 sWAR)"
"ℹ️ **Pre-existing Moves**: 2 scheduled moves (-2.5 sWAR)"
"ℹ️ **Pre-existing Moves**: 1 scheduled moves (no sWAR impact)"
# No display when no pre-existing moves (clean interface)
Complete Embed Structure
The enhanced transaction embed now includes:
- Current Moves - List of moves in transaction builder
- Roster Status - Legal/illegal roster counts with limits
- Team Cost (sWAR) - sWAR for both rosters
- Transaction Context - Pre-existing moves impact (conditional)
- Errors/Suggestions - Validation feedback and recommendations
Usage Examples
Basic Transaction Display
# Standard transaction without pre-existing moves
builder = get_transaction_builder(user_id, team)
embed = await create_transaction_embed(builder)
# Shows: moves, roster status, sWAR, errors/suggestions
Enhanced Context Display
# Transaction with pre-existing moves context
validation = await builder.validate_transaction(next_week=current_week + 1)
embed = await create_transaction_embed(builder)
# Shows: all above + pre-existing transaction impact
User Experience Improvements
- Complete Context: Users see full impact including scheduled moves
- Visual Clarity: Consistent emoji usage and formatting
- Conditional Display: Context only shown when relevant
- Decision Support: sWAR projections help strategic planning
Implementation Notes
- Backwards Compatible: Existing embed functionality preserved
- Conditional Sections: Pre-existing context only appears when applicable
- Performance: Validation data cached to avoid repeated calculations
- Accessibility: Clear visual hierarchy with emojis and formatting
Next Steps for AI Agents:
- Review existing view implementations for patterns
- Understand the Discord UI component system
- Follow the EmbedTemplate system for consistent styling
- Implement proper error handling and user validation
- Test interactive components thoroughly
- Consider accessibility and user experience in design
- Leverage enhanced transaction context for better user guidance