From e35c270f026221701e2f05d4e0391b11c96972c8 Mon Sep 17 00:00:00 2001 From: Greg Date: Sat, 10 May 2025 10:27:20 +0200 Subject: [PATCH] feat: create initial sport attendance system with Flask backend and interactive table UI --- .gitignore | 1 - Input/Coding patterns preferences | 12 ++++ Input/Sport Attendance Sheet MVP.md | 54 +++++++++++++++ Input/Sport Attendance Sheet.xlsx | Bin 0 -> 12342 bytes app.py | 46 +++++++++++++ attendance_data.json | 21 ++++++ requirements.txt | 1 + static/app.js | 98 ++++++++++++++++++++++++++++ templates/index.html | 23 +++++++ 9 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 Input/Coding patterns preferences create mode 100644 Input/Sport Attendance Sheet MVP.md create mode 100644 Input/Sport Attendance Sheet.xlsx create mode 100644 app.py create mode 100644 attendance_data.json create mode 100644 requirements.txt create mode 100644 static/app.js create mode 100644 templates/index.html diff --git a/.gitignore b/.gitignore index 30e9aa1..1db4877 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ .env node_modules -/Input .windsurfrules \ No newline at end of file diff --git a/Input/Coding patterns preferences b/Input/Coding patterns preferences new file mode 100644 index 0000000..6e1490b --- /dev/null +++ b/Input/Coding patterns preferences @@ -0,0 +1,12 @@ +# Coding pattern preferences + +– Always prefer simple solutions +– Avoid duplication of code whenever possible, which means checking for other areas of the codebase that might already have similar code and functionality +– You are careful to only make changes that are requested or you are confident are well understood and related to the change being requested +– When fixing an issue or bug, do not introduce a new pattern or technology without first exhausting all options for the existing implementation. And if you finally do this, make sure to remove the old implementation afterwards so we don't have duplicate logic. +– Keep the codebase very clean and organized +– Avoid writing scripts in files if possible, especially if the script is likely only to be run once +– Avoid having files over 200–300 lines of code. Refactor at that point. +– Mocking data is only needed for tests, never mock data for dev or prod +– Never add stubbing or fake data patterns to code that affects the dev or prod environments +– Never overwrite my .env file without first asking and confirming diff --git a/Input/Sport Attendance Sheet MVP.md b/Input/Sport Attendance Sheet MVP.md new file mode 100644 index 0000000..35ab262 --- /dev/null +++ b/Input/Sport Attendance Sheet MVP.md @@ -0,0 +1,54 @@ +**Core Features:** + +1. **Attendance Table** + - A table with: + - Rows: Each game date (in DD/MM/YY format). + - Columns: Each player’s name (8 fixed names) and one column for a guest (labeled “Guest” or “Mystery”). + - Each cell (except for the date) is clickable to mark attendance (“Yes” or blank). + - The user should be able to adapt the name of the players. + +2. **Add/Edit Dates** + - Ability to add a new date (row) to the table. + +3. **Mark Attendance** + - Clicking a cell toggles attendance for that player/guest on that date. + - “Yes” means attending; blank means not attending. + - The user should be able to adapt the name of the guest. + - The user should be able to adapt the name of the players. + - The user should be able only to choose from Yes or blank to mark attendance. + +4. **Data Persistence** + - The table’s state (who is attending which date) is saved and loaded automatically (from a JSON file). + - No login or authentication needed (handled by your reverse proxy). + +5. **Guest/Mystery Player** + - The last column is always for a guest. You can leave the name as “Guest” or allow it to be edited per date. + +--- + +**What you don’t need for MVP:** +- User registration, login, or roles. +- Notifications, reminders, or analytics. +- Player management (names are fixed). +- Complex UI-just a simple table. + +--- + +**How it works (user flow):** +- User opens the web app. +- Sees a table with upcoming dates and player names. +- Clicks on their name under a date to mark themselves as attending (“Yes”). +- Guest attendance can also be marked. +- Data is saved automatically. + +--- + +**Technical outline:** +- **Frontend:** Simple HTML table, checkboxes or clickable cells, minimal CSS. +- **Backend:** JSON file for storing attendance. +- **No authentication logic needed** (handled at the proxy level). + +--- + +**Summary:** +You only need a simple, editable attendance table with dates as rows and player names (plus guest) as columns. Users can mark their attendance with a click. No login, no user management, just a fast and easy attendance tracker. \ No newline at end of file diff --git a/Input/Sport Attendance Sheet.xlsx b/Input/Sport Attendance Sheet.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..8a9847a70c7dd15a1f500deea0b7ccb3537b1fc1 GIT binary patch literal 12342 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 literal 0 HcmV?d00001 diff --git a/app.py b/app.py new file mode 100644 index 0000000..a7ff33f --- /dev/null +++ b/app.py @@ -0,0 +1,46 @@ +from flask import Flask, render_template, request, jsonify +import json +import os + +app = Flask(__name__) +DATA_FILE = 'attendance_data.json' + +# Default player names +DEFAULT_PLAYERS = [ + "Alice", "Bob", "Charlie", "David", "Eve", "Frank", "Grace", "Hannah" +] +DEFAULT_GUEST = "Guest" + +def load_data(): + if not os.path.exists(DATA_FILE): + data = { + "players": DEFAULT_PLAYERS, + "guest": DEFAULT_GUEST, + "dates": [], + "attendance": {} + } + with open(DATA_FILE, 'w') as f: + json.dump(data, f) + with open(DATA_FILE, 'r') as f: + return json.load(f) + +def save_data(data): + with open(DATA_FILE, 'w') as f: + json.dump(data, f, indent=2) + +@app.route('/') +def index(): + return render_template('index.html') + +@app.route('/api/data', methods=['GET']) +def get_data(): + return jsonify(load_data()) + +@app.route('/api/data', methods=['POST']) +def update_data(): + data = request.json + save_data(data) + return jsonify({"status": "success"}) + +if __name__ == '__main__': + app.run(debug=True) diff --git a/attendance_data.json b/attendance_data.json new file mode 100644 index 0000000..61ba1d1 --- /dev/null +++ b/attendance_data.json @@ -0,0 +1,21 @@ +{ + "attendance": { + "08/05/25|8": true, + "08/05/25|4": true + }, + "dates": [ + "08/05/25", + "5=5" + ], + "guest": "Guest", + "players": [ + "Alice", + "Bob", + "Charlie", + "David", + "Eve", + "Frank", + "Grace", + "Hannah" + ] +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7e10602 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +flask diff --git a/static/app.js b/static/app.js new file mode 100644 index 0000000..b5a104a --- /dev/null +++ b/static/app.js @@ -0,0 +1,98 @@ +let data = {}; + +function fetchData() { + fetch('/api/data').then(r => r.json()).then(d => { + data = d; + renderTable(); + }); +} + +function saveData() { + fetch('/api/data', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }); +} + +function renderTable() { + const container = document.getElementById('attendance-table'); + container.innerHTML = ''; + const table = document.createElement('table'); + // Header row + const thead = document.createElement('thead'); + const headRow = document.createElement('tr'); + headRow.appendChild(document.createElement('th')).innerText = 'Date'; + data.players.forEach((name, i) => { + const th = document.createElement('th'); + const input = document.createElement('input'); + input.type = 'text'; + input.value = name; + input.onchange = e => { + data.players[i] = e.target.value; + saveData(); + renderTable(); + }; + th.appendChild(input); + headRow.appendChild(th); + }); + // Guest column + const guestTh = document.createElement('th'); + const guestInput = document.createElement('input'); + guestInput.type = 'text'; + guestInput.value = data.guest; + guestInput.onchange = e => { + data.guest = e.target.value; + saveData(); + renderTable(); + }; + guestTh.appendChild(guestInput); + headRow.appendChild(guestTh); + thead.appendChild(headRow); + table.appendChild(thead); + // Body rows + const tbody = document.createElement('tbody'); + data.dates.forEach((date, rowIdx) => { + const tr = document.createElement('tr'); + // Date cell + const dateTd = document.createElement('td'); + dateTd.innerText = date; + tr.appendChild(dateTd); + // Player attendance + [...data.players, data.guest].forEach((player, colIdx) => { + const td = document.createElement('td'); + td.className = 'clickable'; + const key = `${date}|${colIdx}`; + if (data.attendance[key]) { + td.innerText = 'Yes'; + td.classList.add('yes'); + } else { + td.innerText = ''; + } + td.onclick = () => { + if (data.attendance[key]) { + delete data.attendance[key]; + } else { + data.attendance[key] = true; + } + saveData(); + renderTable(); + }; + tr.appendChild(td); + }); + tbody.appendChild(tr); + }); + table.appendChild(tbody); + container.appendChild(table); +} + +document.getElementById('add-date').onclick = function() { + const date = prompt('Enter date (DD/MM/YY):'); + if (date && !data.dates.includes(date)) { + data.dates.push(date); + saveData(); + renderTable(); + } +}; + +window.onload = fetchData; diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..550750e --- /dev/null +++ b/templates/index.html @@ -0,0 +1,23 @@ + + + + + Sport Attendance Sheet + + + +

Sport Attendance Sheet

+
+ + + +