feat(mannschaften): split SUN columns and prepare seasonal team CSVs
This commit is contained in:
@@ -51,7 +51,7 @@
|
|||||||
<!-- Mannschaftsaufstellung -->
|
<!-- Mannschaftsaufstellung -->
|
||||||
<div class="bg-white rounded-xl shadow-lg p-6">
|
<div class="bg-white rounded-xl shadow-lg p-6">
|
||||||
<h2 class="text-2xl font-semibold text-gray-900 mb-6">
|
<h2 class="text-2xl font-semibold text-gray-900 mb-6">
|
||||||
Mannschaftsaufstellung Saison 2025/26
|
Mannschaftsaufstellung Saison {{ mannschaftSeasonLabel }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="grid sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
<div class="grid sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
<div
|
<div
|
||||||
@@ -233,14 +233,17 @@
|
|||||||
Spiele
|
Spiele
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
S/U/N
|
S
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
U
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
N
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Sätze
|
Sätze
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
||||||
Bälle
|
|
||||||
</th>
|
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Punkte
|
Punkte
|
||||||
</th>
|
</th>
|
||||||
@@ -262,13 +265,16 @@
|
|||||||
{{ row.meetings_count ?? '-' }}
|
{{ row.meetings_count ?? '-' }}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
|
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
|
||||||
{{ formatSun(row) }}
|
{{ row.meetings_won ?? 0 }}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
|
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
|
||||||
{{ row.sets_relation || '-' }}
|
{{ row.meetings_tie ?? 0 }}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
|
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
|
||||||
{{ row.games_relation || '-' }}
|
{{ row.meetings_lost ?? 0 }}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
|
||||||
|
{{ formatSaetze(row) }}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
|
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
|
||||||
{{ formatPunkte(row) }}
|
{{ formatPunkte(row) }}
|
||||||
@@ -364,6 +370,15 @@ const spielplanSeasonLabel = computed(() => {
|
|||||||
return match ? `20${match[1]}/${match[2]}` : ''
|
return match ? `20${match[1]}/${match[2]}` : ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const mannschaftSeasonLabel = computed(() => {
|
||||||
|
if (spielplanSeasonLabel.value) return spielplanSeasonLabel.value
|
||||||
|
|
||||||
|
const now = new Date()
|
||||||
|
const year = now.getFullYear()
|
||||||
|
const start = now.getMonth() >= 6 ? year : year - 1
|
||||||
|
return `${start}/${String(start + 1).slice(-2)}`
|
||||||
|
})
|
||||||
|
|
||||||
async function fetchCsvText(url) {
|
async function fetchCsvText(url) {
|
||||||
const attempt = async () => {
|
const attempt = async () => {
|
||||||
const withBuster = `${url}${url.includes('?') ? '&' : '?'}_t=${Date.now()}`
|
const withBuster = `${url}${url.includes('?') ? '&' : '?'}_t=${Date.now()}`
|
||||||
@@ -650,12 +665,11 @@ const getRowClass = (row) => {
|
|||||||
return 'bg-white'
|
return 'bg-white'
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatSun = (row) => {
|
const formatSaetze = (row) => {
|
||||||
const s = row?.meetings_won
|
const won = row?.sets_won
|
||||||
const u = row?.meetings_tie
|
const lost = row?.sets_lost
|
||||||
const n = row?.meetings_lost
|
if (won == null && lost == null) return row?.sets_relation || '-'
|
||||||
if (s == null && u == null && n == null) return '-'
|
return `${won ?? 0}:${lost ?? 0}`
|
||||||
return `${s ?? 0}/${u ?? 0}/${n ?? 0}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatPunkte = (row) => {
|
const formatPunkte = (row) => {
|
||||||
|
|||||||
@@ -51,8 +51,9 @@ export default defineEventHandler(async (event) => {
|
|||||||
'termine.csv',
|
'termine.csv',
|
||||||
'spielplan.csv'
|
'spielplan.csv'
|
||||||
]
|
]
|
||||||
|
const isSeasonalMannschaftenFile = /^mannschaften_\d{2}--\d{2}\.csv$/.test(String(filename))
|
||||||
|
|
||||||
if (!allowedFiles.includes(filename)) {
|
if (!allowedFiles.includes(filename) && !isSeasonalMannschaftenFile) {
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 403,
|
statusCode: 403,
|
||||||
statusMessage: 'Datei nicht erlaubt'
|
statusMessage: 'Datei nicht erlaubt'
|
||||||
@@ -105,7 +106,9 @@ export default defineEventHandler(async (event) => {
|
|||||||
'termine.csv': [`${cwd}/server/data/public-data/termine.csv`, `${cwd}/../server/data/public-data/termine.csv`],
|
'termine.csv': [`${cwd}/server/data/public-data/termine.csv`, `${cwd}/../server/data/public-data/termine.csv`],
|
||||||
'spielplan.csv': [`${cwd}/server/data/public-data/spielplan.csv`, `${cwd}/../server/data/public-data/spielplan.csv`]
|
'spielplan.csv': [`${cwd}/server/data/public-data/spielplan.csv`, `${cwd}/../server/data/public-data/spielplan.csv`]
|
||||||
}
|
}
|
||||||
const internalPaths = dataTargetsByFile[filename] || []
|
const internalPaths = isSeasonalMannschaftenFile
|
||||||
|
? [`${cwd}/server/data/public-data/${filename}`, `${cwd}/../server/data/public-data/${filename}`]
|
||||||
|
: (dataTargetsByFile[filename] || [])
|
||||||
|
|
||||||
const uniquePaths = [...new Set([...internalPaths])]
|
const uniquePaths = [...new Set([...internalPaths])]
|
||||||
const writeResults = []
|
const writeResults = []
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { promises as fs } from 'fs'
|
import { promises as fs } from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import { getCurrentSeasonSlug, validateSeasonSlug } from '../utils/spielplan-data.js'
|
||||||
|
|
||||||
|
function normalizeSeasonFilename(season) {
|
||||||
|
return `mannschaften_${season}.csv`
|
||||||
|
}
|
||||||
|
|
||||||
async function exists(p) {
|
async function exists(p) {
|
||||||
try {
|
try {
|
||||||
@@ -13,11 +18,26 @@ async function exists(p) {
|
|||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
try {
|
try {
|
||||||
const cwd = process.cwd()
|
const cwd = process.cwd()
|
||||||
const filename = 'mannschaften.csv'
|
const query = getQuery(event)
|
||||||
|
const requestedSeason = query.season ? String(query.season).trim() : ''
|
||||||
|
|
||||||
|
if (requestedSeason && !validateSeasonSlug(requestedSeason)) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 400,
|
||||||
|
statusMessage: 'Ungueltiger Saison-Slug'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultSeason = getCurrentSeasonSlug()
|
||||||
|
const candidateFileNames = requestedSeason
|
||||||
|
? [normalizeSeasonFilename(requestedSeason), 'mannschaften.csv']
|
||||||
|
: [normalizeSeasonFilename(defaultSeason), 'mannschaften.csv']
|
||||||
|
|
||||||
// Prefer CMS write target first (server/data/public-data),
|
// Prefer CMS write target first (server/data/public-data),
|
||||||
// then legacy locations.
|
// then legacy locations.
|
||||||
const candidates = [
|
const candidates = []
|
||||||
|
for (const filename of candidateFileNames) {
|
||||||
|
candidates.push(
|
||||||
path.join(cwd, 'server/data/public-data', filename),
|
path.join(cwd, 'server/data/public-data', filename),
|
||||||
path.join(cwd, '../server/data/public-data', filename),
|
path.join(cwd, '../server/data/public-data', filename),
|
||||||
path.join(cwd, '.output/server/data', filename),
|
path.join(cwd, '.output/server/data', filename),
|
||||||
@@ -26,7 +46,8 @@ export default defineEventHandler(async (event) => {
|
|||||||
path.join(cwd, 'public/data', filename),
|
path.join(cwd, 'public/data', filename),
|
||||||
path.join(cwd, '../.output/public/data', filename),
|
path.join(cwd, '../.output/public/data', filename),
|
||||||
path.join(cwd, '../public/data', filename)
|
path.join(cwd, '../public/data', filename)
|
||||||
]
|
)
|
||||||
|
}
|
||||||
|
|
||||||
let csvPath = null
|
let csvPath = null
|
||||||
for (const p of candidates) {
|
for (const p of candidates) {
|
||||||
|
|||||||
Reference in New Issue
Block a user