新增讀取 Telegram 出席名單功能,讀取指定日期改為自動 fallback

根本原因:
Node-RED 的 Telegram bot「建喵打羽球」已把報名名單寫進同庫的 attendance 表,但配對器沒有任何入口讀取,每次仍需手動把出席者一個個打進名單。

影響:
- 新增 GET /api/attendance/:time,用既有 pool 讀 attendance 表,沒資料回 404
- 「讀取指定日期」改為兩段:先讀 badminton 配對資料,該日還沒配對時自動改載入出席名單
- 新增「讀取今日出席」按鈕可直接載入出席名單
- attendance 表沒有分區資訊,出席者一律先放 A 區、清空 B 區與既有配對,由使用者自行分配
- badminton 表結構與上傳流程不變

修法:
server.mjs 新增 attendance 端點;App.tsx 新增 findAttendance / loadAttendance / applyAttendance,loadResults 改用 findMatchResults 判斷後 fallback,移除不再使用的 loadMatchResults;README 補上讀取今日出席章節與 attendance 表結構。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-17 11:46:13 +08:00
co-authored by Claude Fable 5
parent 4633412407
commit 121a5fee4c
3 changed files with 185 additions and 21 deletions
+51
View File
@@ -8,6 +8,7 @@ import { fileURLToPath } from 'node:url'
const app = express()
const port = Number(process.env.PORT ?? process.env.SERVER_PORT ?? 8787)
const tableName = process.env.DB_TABLE ?? 'badminton'
const attendanceTableName = 'attendance'
const currentFilePath = fileURLToPath(import.meta.url)
const currentDir = path.dirname(currentFilePath)
@@ -154,6 +155,56 @@ app.get('/api/match-results/:time', async (request, response) => {
}
})
app.get('/api/attendance/:time', async (request, response) => {
if (!pool) {
response.status(500).json({
ok: false,
message: `資料庫環境變數缺少:${missingEnv.join(', ')}`,
})
return
}
const time = String(request.params.time ?? '')
if (!/^\d{8}$/.test(time)) {
response.status(400).json({
ok: false,
message: '日期格式不正確,請使用 YYYYMMDD。',
})
return
}
try {
const [rows] = await pool.execute(
`SELECT nickname FROM \`${attendanceTableName}\` WHERE poll_date = ? ORDER BY joined_at ASC, user_id ASC`,
[Number(time)],
)
const names = rows
.map((row) => String(row.nickname ?? '').trim())
.filter(Boolean)
if (names.length === 0) {
response.status(404).json({
ok: false,
message: '指定日期沒有出席資料。',
})
return
}
response.json({
ok: true,
data: { time: Number(time), names },
})
} catch (error) {
console.error('attendance load error:', error)
response.status(500).json({
ok: false,
message: error instanceof Error ? error.message : '出席資料讀取失敗。',
})
}
})
app.post('/api/match-results', async (request, response) => {
if (!pool) {
response.status(500).json({