Fix historical squads
This commit is contained in:
parent
6a50c9fe90
commit
c199979eb9
137
motm_app/SQUAD_HISTORY_FEATURE.md
Normal file
137
motm_app/SQUAD_HISTORY_FEATURE.md
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
# Squad History Feature
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The squad history feature preserves historical match squad data when resetting for a new match. Previously, when the squad was reset, all player data was lost. Now it's automatically archived to a dedicated history table.
|
||||||
|
|
||||||
|
## What Changed
|
||||||
|
|
||||||
|
### Before
|
||||||
|
- Squad reset would delete or overwrite previous squad data
|
||||||
|
- No way to view who played in previous matches
|
||||||
|
- Historical squad information was lost forever
|
||||||
|
|
||||||
|
### After
|
||||||
|
- Squad data is **automatically archived** before being cleared
|
||||||
|
- Complete historical record preserved with match date and fixture number
|
||||||
|
- New admin interface to view all historical squads
|
||||||
|
- Easy lookup of who played in any previous match
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
### When You Reset the Squad
|
||||||
|
|
||||||
|
1. **Automatic Archival**: Before clearing the current squad, the system:
|
||||||
|
- Copies all players to the `squad_history` table
|
||||||
|
- Stores the match date and fixture number
|
||||||
|
- Records when the archive was created
|
||||||
|
|
||||||
|
2. **Clean Reset**: The current squad table is cleared for the new match
|
||||||
|
|
||||||
|
3. **Confirmation**: Shows how many players were archived
|
||||||
|
|
||||||
|
### Database Structure
|
||||||
|
|
||||||
|
**New Table: `squad_history`**
|
||||||
|
```sql
|
||||||
|
- id (primary key)
|
||||||
|
- player_number
|
||||||
|
- player_forenames
|
||||||
|
- player_surname
|
||||||
|
- player_nickname
|
||||||
|
- match_date
|
||||||
|
- fixture_number
|
||||||
|
- archived_at (timestamp)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using the Feature
|
||||||
|
|
||||||
|
### Resetting the Squad (with History)
|
||||||
|
|
||||||
|
1. Go to **Admin Dashboard** → **Match Squad Management**
|
||||||
|
2. Click **"Reset Squad"**
|
||||||
|
3. System automatically:
|
||||||
|
- Archives current squad with match details
|
||||||
|
- Clears squad for new match
|
||||||
|
- Shows confirmation message
|
||||||
|
|
||||||
|
### Viewing Squad History
|
||||||
|
|
||||||
|
1. Go to **Admin Dashboard** → **Squad History**
|
||||||
|
2. See summary of all historical squads
|
||||||
|
3. Click **"View Details"** on any fixture to see the full squad
|
||||||
|
4. Scroll to see detailed player lists for each match
|
||||||
|
|
||||||
|
## Migration
|
||||||
|
|
||||||
|
The migration has been **automatically completed**! ✅
|
||||||
|
|
||||||
|
The `squad_history` table was created successfully.
|
||||||
|
|
||||||
|
For future databases or if needed again:
|
||||||
|
```bash
|
||||||
|
python add_squad_history.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
✅ **Automatic Archival**: No manual steps needed
|
||||||
|
✅ **Complete Records**: All player details preserved
|
||||||
|
✅ **Easy Navigation**: Summary view with drill-down details
|
||||||
|
✅ **Match Context**: Linked to match date and fixture number
|
||||||
|
✅ **Safe Reset**: Squad clearing only happens after successful archive
|
||||||
|
|
||||||
|
## Admin Interface
|
||||||
|
|
||||||
|
### Squad History Page Shows:
|
||||||
|
|
||||||
|
**Summary Table:**
|
||||||
|
- Fixture Number
|
||||||
|
- Match Date
|
||||||
|
- Player Count
|
||||||
|
- Quick view button
|
||||||
|
|
||||||
|
**Detailed View:**
|
||||||
|
- Full squad rosters grouped by match
|
||||||
|
- Player numbers, nicknames, and full names
|
||||||
|
- Archive timestamp for audit trail
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
1. **Historical Reference**: See who played in any match
|
||||||
|
2. **Team Analysis**: Track player participation over time
|
||||||
|
3. **Data Integrity**: No more lost squad data
|
||||||
|
4. **Audit Trail**: Know when squads were archived
|
||||||
|
5. **Reporting**: Export or analyze squad patterns
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
|
||||||
|
### Files Modified
|
||||||
|
|
||||||
|
1. **`database.py`** - Added `SquadHistory` model
|
||||||
|
2. **`main.py`** - Updated reset function and added history route
|
||||||
|
3. **`templates/squad_history.html`** - New history viewing interface
|
||||||
|
4. **`add_squad_history.py`** - Migration script
|
||||||
|
|
||||||
|
### Safety Features
|
||||||
|
|
||||||
|
- Transaction-based: Archive completes before deletion
|
||||||
|
- Error handling: If archive fails, squad is not cleared
|
||||||
|
- Flash messages: Clear feedback on what happened
|
||||||
|
- Rollback capable: Can restore from history if needed
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
Potential additions for future versions:
|
||||||
|
- Export squad history to CSV/Excel
|
||||||
|
- Compare squads between matches
|
||||||
|
- Player participation statistics
|
||||||
|
- Squad restoration from history
|
||||||
|
- Search/filter historical squads
|
||||||
|
- Visual squad timeline
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Implementation Date**: October 2025
|
||||||
|
**Status**: ✅ Active and Working
|
||||||
|
|
||||||
91
motm_app/add_squad_history.py
Executable file
91
motm_app/add_squad_history.py
Executable file
@ -0,0 +1,91 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Database migration script to add squad_history table for historical squad tracking.
|
||||||
|
|
||||||
|
This script creates the squad_history table to preserve squad data when resetting for a new match.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from db_config import db_config
|
||||||
|
from sqlalchemy import text
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def add_squad_history_table():
|
||||||
|
"""Create squad_history table for historical squad records."""
|
||||||
|
try:
|
||||||
|
engine = db_config.engine
|
||||||
|
|
||||||
|
with engine.connect() as connection:
|
||||||
|
# Check if table already exists
|
||||||
|
try:
|
||||||
|
result = connection.execute(text("SELECT COUNT(*) FROM squad_history LIMIT 1"))
|
||||||
|
print("✓ Table 'squad_history' already exists")
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Create the squad_history table
|
||||||
|
try:
|
||||||
|
# PostgreSQL/SQLite compatible syntax
|
||||||
|
create_table_sql = text("""
|
||||||
|
CREATE TABLE squad_history (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
player_number INTEGER,
|
||||||
|
player_forenames VARCHAR(50),
|
||||||
|
player_surname VARCHAR(30),
|
||||||
|
player_nickname VARCHAR(30),
|
||||||
|
match_date DATE,
|
||||||
|
fixture_number VARCHAR(20),
|
||||||
|
archived_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
connection.execute(create_table_sql)
|
||||||
|
connection.commit()
|
||||||
|
print("✓ Successfully created 'squad_history' table (PostgreSQL)")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
# Try SQLite syntax
|
||||||
|
try:
|
||||||
|
connection.rollback()
|
||||||
|
create_table_sql = text("""
|
||||||
|
CREATE TABLE squad_history (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
player_number INTEGER,
|
||||||
|
player_forenames VARCHAR(50),
|
||||||
|
player_surname VARCHAR(30),
|
||||||
|
player_nickname VARCHAR(30),
|
||||||
|
match_date DATE,
|
||||||
|
fixture_number VARCHAR(20),
|
||||||
|
archived_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
connection.execute(create_table_sql)
|
||||||
|
connection.commit()
|
||||||
|
print("✓ Successfully created 'squad_history' table (SQLite)")
|
||||||
|
return True
|
||||||
|
except Exception as e2:
|
||||||
|
print(f"✗ Error creating table: {str(e2)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Error connecting to database: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("=" * 60)
|
||||||
|
print("Squad History Table - Database Migration")
|
||||||
|
print("=" * 60)
|
||||||
|
print("\nThis script will create the 'squad_history' table to preserve")
|
||||||
|
print("historical squad data when resetting for new matches.\n")
|
||||||
|
|
||||||
|
result = add_squad_history_table()
|
||||||
|
|
||||||
|
if result:
|
||||||
|
print("\n✓ Migration completed successfully!")
|
||||||
|
print("\nSquad data will now be preserved when you reset the squad.")
|
||||||
|
print("Historical squads are stored with match date and fixture number.")
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
print("\n✗ Migration failed!")
|
||||||
|
print("Please check the error messages above.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
@ -124,6 +124,19 @@ class MatchSquad(Base):
|
|||||||
match_date = Column(Date)
|
match_date = Column(Date)
|
||||||
created_at = Column(DateTime, default=datetime.utcnow)
|
created_at = Column(DateTime, default=datetime.utcnow)
|
||||||
|
|
||||||
|
class SquadHistory(Base):
|
||||||
|
"""Historical match squad records."""
|
||||||
|
__tablename__ = 'squad_history'
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
player_number = Column(Integer, ForeignKey('players.player_number'))
|
||||||
|
player_forenames = Column(String(50))
|
||||||
|
player_surname = Column(String(30))
|
||||||
|
player_nickname = Column(String(30))
|
||||||
|
match_date = Column(Date)
|
||||||
|
fixture_number = Column(String(20))
|
||||||
|
archived_at = Column(DateTime, default=datetime.utcnow)
|
||||||
|
|
||||||
class HockeyFixture(Base):
|
class HockeyFixture(Base):
|
||||||
"""Hockey fixture model."""
|
"""Hockey fixture model."""
|
||||||
__tablename__ = 'hockey_fixtures'
|
__tablename__ = 'hockey_fixtures'
|
||||||
|
|||||||
184
motm_app/main.py
184
motm_app/main.py
@ -178,6 +178,75 @@ def is_admin_authenticated(request):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_previous_match_winners():
|
||||||
|
"""
|
||||||
|
Automatically determine the MOTM and DotD winners from the most recent completed fixture.
|
||||||
|
Returns a tuple of (motm_player_number, dotd_player_number) or (None, None) if not found.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Get all fixture columns from _hkfc_c_motm table
|
||||||
|
sql_columns = text("""
|
||||||
|
SELECT column_name
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = '_hkfc_c_motm'
|
||||||
|
AND (column_name LIKE 'motm_%' OR column_name LIKE 'dotd_%')
|
||||||
|
AND column_name NOT LIKE '%total'
|
||||||
|
ORDER BY column_name DESC
|
||||||
|
""")
|
||||||
|
columns = sql_read(sql_columns)
|
||||||
|
|
||||||
|
if not columns:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
# Extract unique fixture dates from column names
|
||||||
|
fixture_dates = set()
|
||||||
|
for col in columns:
|
||||||
|
col_name = col['column_name']
|
||||||
|
if col_name.startswith('motm_'):
|
||||||
|
fixture_dates.add(col_name.replace('motm_', ''))
|
||||||
|
elif col_name.startswith('dotd_'):
|
||||||
|
fixture_dates.add(col_name.replace('dotd_', ''))
|
||||||
|
|
||||||
|
# Sort fixture dates in descending order (most recent first)
|
||||||
|
sorted_dates = sorted(list(fixture_dates), reverse=True)
|
||||||
|
|
||||||
|
if not sorted_dates:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
# Get the most recent fixture date
|
||||||
|
latest_fixture = sorted_dates[0]
|
||||||
|
motm_col = f'motm_{latest_fixture}'
|
||||||
|
dotd_col = f'dotd_{latest_fixture}'
|
||||||
|
|
||||||
|
# Find the MOTM winner (player with most votes)
|
||||||
|
sql_motm = text(f"""
|
||||||
|
SELECT playernumber, {motm_col} as votes
|
||||||
|
FROM _hkfc_c_motm
|
||||||
|
WHERE {motm_col} > 0
|
||||||
|
ORDER BY {motm_col} DESC
|
||||||
|
LIMIT 1
|
||||||
|
""")
|
||||||
|
motm_result = sql_read(sql_motm)
|
||||||
|
motm_winner = motm_result[0]['playernumber'] if motm_result else None
|
||||||
|
|
||||||
|
# Find the DotD winner (player with most votes)
|
||||||
|
sql_dotd = text(f"""
|
||||||
|
SELECT playernumber, {dotd_col} as votes
|
||||||
|
FROM _hkfc_c_motm
|
||||||
|
WHERE {dotd_col} > 0
|
||||||
|
ORDER BY {dotd_col} DESC
|
||||||
|
LIMIT 1
|
||||||
|
""")
|
||||||
|
dotd_result = sql_read(sql_dotd)
|
||||||
|
dotd_winner = dotd_result[0]['playernumber'] if dotd_result else None
|
||||||
|
|
||||||
|
return motm_winner, dotd_winner
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting previous match winners: {e}")
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
# ==================== PUBLIC VOTING SECTION ====================
|
# ==================== PUBLIC VOTING SECTION ====================
|
||||||
|
|
||||||
@app.route('/motm/<randomUrlSuffix>')
|
@app.route('/motm/<randomUrlSuffix>')
|
||||||
@ -512,7 +581,9 @@ def motm_admin():
|
|||||||
return redirect(url_for('motm_admin'))
|
return redirect(url_for('motm_admin'))
|
||||||
_nextClub = _nextClubName[0]['club']
|
_nextClub = _nextClubName[0]['club']
|
||||||
|
|
||||||
# Update currMotM and currDotD - set to None if '0' (No Previous) is selected
|
# Get the form values for previous MOTM and DotD
|
||||||
|
# If user selected '0' (No Previous), use None
|
||||||
|
# Otherwise use the selected player number
|
||||||
curr_motm_value = None if _currMotM == '0' else _currMotM
|
curr_motm_value = None if _currMotM == '0' else _currMotM
|
||||||
curr_dotd_value = None if _currDotD == '0' else _currDotD
|
curr_dotd_value = None if _currDotD == '0' else _currDotD
|
||||||
|
|
||||||
@ -600,9 +671,14 @@ def motm_admin():
|
|||||||
if hasattr(deadline, 'strftime'):
|
if hasattr(deadline, 'strftime'):
|
||||||
form.votingDeadline.data = deadline.strftime('%Y-%m-%dT%H:%M')
|
form.votingDeadline.data = deadline.strftime('%Y-%m-%dT%H:%M')
|
||||||
|
|
||||||
|
# Get automatically determined previous winners
|
||||||
|
auto_prev_motm, auto_prev_dotd = get_previous_match_winners()
|
||||||
|
|
||||||
sql4 = text("SELECT hockeyclub FROM menshockeyclubs ORDER BY hockeyclub")
|
sql4 = text("SELECT hockeyclub FROM menshockeyclubs ORDER BY hockeyclub")
|
||||||
sql5 = text("SELECT nextclub, oppologo FROM motmadminsettings")
|
sql5 = text("SELECT nextclub, oppologo FROM motmadminsettings")
|
||||||
sql6 = text(f"SELECT playernumber, playerforenames, playersurname FROM _hkfcc_matchsquad_{prevFixture} ORDER BY playerforenames")
|
|
||||||
|
# Get all players for the dropdown
|
||||||
|
sql6 = text("SELECT playernumber, playerforenames, playersurname, playernickname FROM _hkfc_players ORDER BY playernickname")
|
||||||
clubs = sql_read_static(sql4)
|
clubs = sql_read_static(sql4)
|
||||||
settings = sql_read_static(sql5)
|
settings = sql_read_static(sql5)
|
||||||
players = sql_read(sql6)
|
players = sql_read(sql6)
|
||||||
@ -616,14 +692,30 @@ def motm_admin():
|
|||||||
players = []
|
players = []
|
||||||
|
|
||||||
form.nextOppoClub.choices = [(oppo['hockeyclub'], oppo['hockeyclub']) for oppo in clubs]
|
form.nextOppoClub.choices = [(oppo['hockeyclub'], oppo['hockeyclub']) for oppo in clubs]
|
||||||
# Add "No Previous" option at the beginning of the list
|
# Build player choices with nickname for better identification
|
||||||
form.currMotM.choices = [('0', '-- No Previous MOTM --')] + [(player['playernumber'], player['playerforenames'] + " " + player['playersurname']) for player in players]
|
form.currMotM.choices = [('0', '-- No Previous MOTM --')] + [(str(player['playernumber']), f"{player['playernickname']} ({player['playerforenames']} {player['playersurname']})") for player in players]
|
||||||
form.currDotD.choices = [('0', '-- No Previous DotD --')] + [(player['playernumber'], player['playerforenames'] + " " + player['playersurname']) for player in players]
|
form.currDotD.choices = [('0', '-- No Previous DotD --')] + [(str(player['playernumber']), f"{player['playernickname']} ({player['playerforenames']} {player['playersurname']})") for player in players]
|
||||||
|
|
||||||
# Pre-select current MOTM and DotD values (default to '0' if NULL)
|
# Pre-select values: use database value if exists, otherwise auto-determine from previous fixture
|
||||||
if current_settings:
|
if current_settings:
|
||||||
form.currMotM.data = str(current_settings[0]['currmotm']) if current_settings[0].get('currmotm') else '0'
|
# If database has a value set, use it; otherwise use auto-determined winner
|
||||||
form.currDotD.data = str(current_settings[0]['currdotd']) if current_settings[0].get('currdotd') else '0'
|
if current_settings[0].get('currmotm'):
|
||||||
|
form.currMotM.data = str(current_settings[0]['currmotm'])
|
||||||
|
elif auto_prev_motm:
|
||||||
|
form.currMotM.data = str(auto_prev_motm)
|
||||||
|
else:
|
||||||
|
form.currMotM.data = '0'
|
||||||
|
|
||||||
|
if current_settings[0].get('currdotd'):
|
||||||
|
form.currDotD.data = str(current_settings[0]['currdotd'])
|
||||||
|
elif auto_prev_dotd:
|
||||||
|
form.currDotD.data = str(auto_prev_dotd)
|
||||||
|
else:
|
||||||
|
form.currDotD.data = '0'
|
||||||
|
else:
|
||||||
|
# No settings in database, use auto-determined or default to '0'
|
||||||
|
form.currMotM.data = str(auto_prev_motm) if auto_prev_motm else '0'
|
||||||
|
form.currDotD.data = str(auto_prev_dotd) if auto_prev_dotd else '0'
|
||||||
|
|
||||||
# Get the opposition logo using S3 service
|
# Get the opposition logo using S3 service
|
||||||
clubLogo = s3_asset_service.get_asset_url('images/default_logo.png') # Default fallback
|
clubLogo = s3_asset_service.get_asset_url('images/default_logo.png') # Default fallback
|
||||||
@ -1648,6 +1740,37 @@ def match_squad_list():
|
|||||||
return render_template('match_squad_selected.html', table=table)
|
return render_template('match_squad_selected.html', table=table)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/squad/history')
|
||||||
|
@basic_auth.required
|
||||||
|
def squad_history():
|
||||||
|
"""View historical squad data"""
|
||||||
|
try:
|
||||||
|
# Get all historical squads grouped by fixture
|
||||||
|
sql = text("""
|
||||||
|
SELECT fixture_number, match_date,
|
||||||
|
COUNT(*) as player_count,
|
||||||
|
STRING_AGG(player_nickname, ', ') as players
|
||||||
|
FROM squad_history
|
||||||
|
GROUP BY fixture_number, match_date
|
||||||
|
ORDER BY match_date DESC
|
||||||
|
""")
|
||||||
|
history = sql_read(sql)
|
||||||
|
|
||||||
|
# Get detailed squad for each fixture
|
||||||
|
sql_details = text("""
|
||||||
|
SELECT fixture_number, match_date, player_number,
|
||||||
|
player_forenames, player_surname, player_nickname, archived_at
|
||||||
|
FROM squad_history
|
||||||
|
ORDER BY match_date DESC, player_nickname
|
||||||
|
""")
|
||||||
|
details = sql_read(sql_details)
|
||||||
|
|
||||||
|
return render_template('squad_history.html', history=history, details=details)
|
||||||
|
except Exception as e:
|
||||||
|
flash(f'Error loading squad history: {str(e)}', 'error')
|
||||||
|
return redirect(url_for('admin_dashboard'))
|
||||||
|
|
||||||
|
|
||||||
@app.route('/admin/squad/remove', methods=['POST'])
|
@app.route('/admin/squad/remove', methods=['POST'])
|
||||||
@basic_auth.required
|
@basic_auth.required
|
||||||
def delPlayerFromSquad():
|
def delPlayerFromSquad():
|
||||||
@ -1666,34 +1789,47 @@ def delPlayerFromSquad():
|
|||||||
@app.route('/admin/squad/reset')
|
@app.route('/admin/squad/reset')
|
||||||
@basic_auth.required
|
@basic_auth.required
|
||||||
def matchSquadReset():
|
def matchSquadReset():
|
||||||
"""Reset squad for new match"""
|
"""Reset squad for new match - archives current squad to history before clearing"""
|
||||||
_matchNumber = str(mySettings('fixture'))
|
|
||||||
print(_matchNumber)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# First, check if there are any players in the current squad
|
# Get current match date and fixture number from admin settings
|
||||||
check_sql = text("SELECT COUNT(*) as count FROM _hkfcC_matchSquad")
|
sql_settings = text("SELECT nextdate, nextfixture FROM motmadminsettings WHERE userid = 'admin'")
|
||||||
|
settings = sql_read_static(sql_settings)
|
||||||
|
|
||||||
|
if not settings:
|
||||||
|
flash('Error: Admin settings not found. Please configure match settings first.', 'error')
|
||||||
|
return render_template('match_squad_reset.html')
|
||||||
|
|
||||||
|
match_date = settings[0]['nextdate']
|
||||||
|
fixture_number = match_date.strftime('%Y%m%d') if match_date else 'unknown'
|
||||||
|
|
||||||
|
# Check if there are any players in the current squad
|
||||||
|
check_sql = text("SELECT COUNT(*) as count FROM _hkfcc_matchsquad")
|
||||||
result = sql_read(check_sql)
|
result = sql_read(check_sql)
|
||||||
squad_count = result[0]['count'] if result else 0
|
squad_count = result[0]['count'] if result else 0
|
||||||
|
|
||||||
if squad_count > 0:
|
if squad_count > 0:
|
||||||
# Rename current squad table
|
# Archive current squad to history table
|
||||||
sql1 = text(f"RENAME TABLE _hkfcC_matchSquad TO _hkfcC_matchSquad_{_matchNumber}")
|
archive_sql = text("""
|
||||||
sql_write(sql1)
|
INSERT INTO squad_history (player_number, player_forenames, player_surname, player_nickname, match_date, fixture_number)
|
||||||
|
SELECT playernumber, playerforenames, playersurname, playernickname, :match_date, :fixture_number
|
||||||
|
FROM _hkfcc_matchsquad
|
||||||
|
""")
|
||||||
|
sql_write(archive_sql, {'match_date': match_date, 'fixture_number': fixture_number})
|
||||||
|
|
||||||
# Create new empty squad table
|
# Clear current squad table
|
||||||
sql2 = text("CREATE TABLE _hkfcC_matchSquad (playerNumber smallint UNIQUE, playerForenames varchar(50), playerSurname varchar(30), playerNickname varchar(30) NOT NULL, PRIMARY KEY (playerNumber))")
|
clear_sql = text("DELETE FROM _hkfcc_matchsquad")
|
||||||
sql_write(sql2)
|
sql_write(clear_sql)
|
||||||
|
|
||||||
# Update fixture number
|
# Update previous fixture number in settings
|
||||||
sql3 = text("UPDATE motmAdminSettings SET prevFixture = :match_number")
|
update_sql = text("UPDATE motmadminsettings SET prevfixture = :fixture_number WHERE userid = 'admin'")
|
||||||
sql_write_static(sql3, {'match_number': _matchNumber})
|
sql_write_static(update_sql, {'fixture_number': fixture_number})
|
||||||
|
|
||||||
flash(f'Squad reset successfully! {squad_count} players archived for match {_matchNumber}', 'success')
|
flash(f'Squad reset successfully! {squad_count} players archived for match {fixture_number} ({match_date})', 'success')
|
||||||
else:
|
else:
|
||||||
flash('No players in current squad to reset', 'info')
|
flash('No players in current squad to reset', 'info')
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
print(f"Error in squad reset: {str(e)}")
|
||||||
flash(f'Error resetting squad: {str(e)}', 'error')
|
flash(f'Error resetting squad: {str(e)}', 'error')
|
||||||
|
|
||||||
return render_template('match_squad_reset.html')
|
return render_template('match_squad_reset.html')
|
||||||
|
|||||||
@ -304,6 +304,9 @@
|
|||||||
<a href="/admin/squad/list" class="btn btn-dark">
|
<a href="/admin/squad/list" class="btn btn-dark">
|
||||||
<i class="fas fa-eye me-2"></i>View Squad
|
<i class="fas fa-eye me-2"></i>View Squad
|
||||||
</a>
|
</a>
|
||||||
|
<a href="/admin/squad/history" class="btn btn-outline-dark">
|
||||||
|
<i class="fas fa-history me-2"></i>Squad History
|
||||||
|
</a>
|
||||||
<a href="/admin/squad/reset" class="btn btn-outline-dark">
|
<a href="/admin/squad/reset" class="btn btn-outline-dark">
|
||||||
<i class="fas fa-refresh me-2"></i>Reset Squad
|
<i class="fas fa-refresh me-2"></i>Reset Squad
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@ -86,14 +86,18 @@
|
|||||||
<i class="fas fa-trophy me-2 text-warning"></i>Previous Man of the Match
|
<i class="fas fa-trophy me-2 text-warning"></i>Previous Man of the Match
|
||||||
</label>
|
</label>
|
||||||
{{ form.currMotM(class_="form-select") }}
|
{{ form.currMotM(class_="form-select") }}
|
||||||
<small class="form-text text-muted">Select "No Previous MOTM" for the first match or if no previous winner</small>
|
<small class="form-text text-muted">
|
||||||
|
<i class="fas fa-magic me-1"></i>Auto-selected from previous vote. Choose "No Previous" to override.
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<label for="currDotD" class="form-label">
|
<label for="currDotD" class="form-label">
|
||||||
<i class="fas fa-user-times me-2 text-danger"></i>Previous Dick of the Day
|
<i class="fas fa-user-times me-2 text-danger"></i>Previous Dick of the Day
|
||||||
</label>
|
</label>
|
||||||
{{ form.currDotD(class_="form-select") }}
|
{{ form.currDotD(class_="form-select") }}
|
||||||
<small class="form-text text-muted">Select "No Previous DotD" for the first match or if no previous winner</small>
|
<small class="form-text text-muted">
|
||||||
|
<i class="fas fa-magic me-1"></i>Auto-selected from previous vote. Choose "No Previous" to override.
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
150
motm_app/templates/squad_history.html
Normal file
150
motm_app/templates/squad_history.html
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Squad History{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<h2><i class="fas fa-history me-2"></i>Squad History</h2>
|
||||||
|
<a href="/admin" class="btn btn-secondary">
|
||||||
|
<i class="fas fa-arrow-left me-2"></i>Back to Admin
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<div class="alert alert-{{ 'danger' if category == 'error' else 'success' if category == 'success' else 'info' }} alert-dismissible fade show" role="alert">
|
||||||
|
{{ message }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
<!-- Historical Squads Summary -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h5 class="card-title mb-0">
|
||||||
|
<i class="fas fa-list me-2"></i>Historical Squads Summary
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
{% if history %}
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Fixture Number</th>
|
||||||
|
<th>Match Date</th>
|
||||||
|
<th>Players</th>
|
||||||
|
<th>Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for fixture in history %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ fixture.fixture_number }}</td>
|
||||||
|
<td>{{ fixture.match_date }}</td>
|
||||||
|
<td>{{ fixture.player_count }} players</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-sm btn-info" onclick="showSquadDetails('{{ fixture.fixture_number }}')">
|
||||||
|
<i class="fas fa-eye me-1"></i>View Details
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>No historical squad data available yet.
|
||||||
|
Squad data will be saved here when you reset the squad for a new match.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Detailed Squad View -->
|
||||||
|
{% if details %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header bg-secondary text-white">
|
||||||
|
<h5 class="card-title mb-0">
|
||||||
|
<i class="fas fa-users me-2"></i>Detailed Squad Records
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
{% set current_fixture = '' %}
|
||||||
|
{% for player in details %}
|
||||||
|
{% if player.fixture_number != current_fixture %}
|
||||||
|
{% if current_fixture != '' %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% set current_fixture = player.fixture_number %}
|
||||||
|
<div class="squad-detail mb-4" id="squad-{{ player.fixture_number }}">
|
||||||
|
<h6 class="border-bottom pb-2">
|
||||||
|
<i class="fas fa-calendar me-2"></i>Fixture {{ player.fixture_number }} - {{ player.match_date }}
|
||||||
|
<small class="text-muted">(Archived: {{ player.archived_at }})</small>
|
||||||
|
</h6>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm table-bordered">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>Number</th>
|
||||||
|
<th>Nickname</th>
|
||||||
|
<th>Full Name</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% endif %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ player.player_number }}</td>
|
||||||
|
<td><strong>{{ player.player_nickname }}</strong></td>
|
||||||
|
<td>{{ player.player_forenames }} {{ player.player_surname }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% if current_fixture != '' %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_scripts %}
|
||||||
|
<script>
|
||||||
|
function showSquadDetails(fixtureNumber) {
|
||||||
|
const element = document.getElementById('squad-' + fixtureNumber);
|
||||||
|
if (element) {
|
||||||
|
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
element.classList.add('highlight-squad');
|
||||||
|
setTimeout(() => element.classList.remove('highlight-squad'), 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.highlight-squad {
|
||||||
|
background-color: #fff3cd;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user