Fiks skjult score-registrering og doble Score-faner på rundens Score-fane
Avstandsmåleren fylte hele mobilskjermen uten å antyde at score- registrering fantes lenger ned (funnet via brukers skjermbilde). Viser nå kompakt som standard med en "Vis banekart"-utvidelse og en alltid synlig "Registrer score"-snarvei. Den separate, duplikate "Score"-pillefanen inni round-detail.tsx (visuelt identisk med RoundHeader sin egen Score/ Scorekort/Leaderboard-rad) er erstattet med én tydelig annerledes veksle-knapp -- fikser samtidig et reelt navigasjonshull der det ikke fantes noen vei tilbake til Score fra en ekstern "?tab=manage"-lenke.
This commit is contained in:
parent
6765d17109
commit
05599aafe6
3 changed files with 144 additions and 27 deletions
62
CHANGELOG.md
62
CHANGELOG.md
|
|
@ -13230,3 +13230,65 @@ Neste steg:
|
|||
|
||||
**Rullet ut 2026-08-19** -- bruker bekreftet. Ren frontend.
|
||||
`docker compose build teecup_frontend && up -d`, rene logger, 200 OK.
|
||||
|
||||
133. **Score-fanen (egen runde): skjult score-registrering + doble
|
||||
"Score"-faner -- funnet via brukers skjermbilde, 2026-08-20.** Bruker
|
||||
viste et ekte skjermbilde: på et hull med koordinatdata fylte
|
||||
avstandsmåleren/hull-diagrammet HELE den synlige mobilskjermen, uten
|
||||
noe som antydet at spillerkortene (selve score-registreringen) fantes
|
||||
lenger ned -- en førstegangsbruker skjønte ikke at de måtte scrolle.
|
||||
Samtidig lå det to visuelt identiske pille-fanerader rett oppå
|
||||
hverandre: RoundHeader sin Score/Scorekort/Leaderboard, og en HELT
|
||||
SEPARAT, egen "Score"/"Spillere og runde"-fanerad bygget inni
|
||||
`round-detail.tsx` selv -- begge kalte én fane "Score", leste som
|
||||
duplikate kontroller.
|
||||
|
||||
Løst via en Claude-skrevet V0-prompt (samme etablerte
|
||||
"Claude skriver prompt, bruker kjører V0, Claude kobler inn"-mønster
|
||||
som resten av appen) -- V0-eksporten (`round-score-top.tsx`) traff
|
||||
begge problemene presist og ble brukt som fasit for restrukturering
|
||||
av de EKTE komponentene (ikke V0s egen mock-`HoleDiagram`, men den
|
||||
ekte `HoleTargetDistance`/`HoleDiagram`, som allerede -- upåaktet --
|
||||
støttet en "compact"-modus, aldri faktisk brukt noe sted i kodebasen
|
||||
før nå):
|
||||
|
||||
- `hole-target-distance.tsx`: ny valgfri `onAvailabilityChange`-prop
|
||||
-- kalleren får vite om banen faktisk har koordinatdata for hullet,
|
||||
uten å duplisere fetch-en selv.
|
||||
- `round-detail.tsx` (`PlayerHoleCards`): avstandsmåleren vises nå
|
||||
`size="compact"` som standard (kun de tre store avstandstallene),
|
||||
med en "Vis banekart og hindringer"-knapp som utvider til det fulle
|
||||
diagrammet -- knappen vises KUN når `onAvailabilityChange` faktisk
|
||||
har meldt at det finnes data (ikke en død knapp på baner uten
|
||||
koordinater). En alltid synlig "Registrer score ↓"-knapp scroller
|
||||
rett til spillerkortene.
|
||||
- `round-detail.tsx` (hovedkomponenten): den separate
|
||||
"Score"/"Spillere og runde"-pille-fanen er fjernet, erstattet med
|
||||
ÉN sekundær, tydelig ANNERLEDES knapp (ramme + ikon, ikke en pille)
|
||||
som veksler `pageTab` mellom de to visningene, med tekst som endrer
|
||||
seg etter tilstand ("Spillere og runde" / "Tilbake til score").
|
||||
|
||||
**Reelt funn underveis, ikke bare kosmetikk:** før denne fiksen
|
||||
fantes det INGEN pålitelig vei tilbake fra "Spillere og runde" til
|
||||
"Score" dersom brukeren kom dit via en ekstern lenke (f.eks.
|
||||
RoundHeader sin Administrer-dialog, som lenker til `?tab=manage`) --
|
||||
`pageTab`-tilstanden ble aldri tilbakestilt automatisk, og den gamle
|
||||
fanen for å bytte tilbake fantes ikke i den konteksten. Den nye
|
||||
veksle-knappen løser dette som et biprodukt, siden den er ren
|
||||
klient-tilstand, ikke avhengig av URL.
|
||||
|
||||
**Verifisert i scratch:** en midlertidig forhåndsvisningsrute som
|
||||
rendret den EKTE `RoundDetail`-komponenten (ikke en isolert mock) med
|
||||
et fullstendig mocket `fetch` (runde, deltakere, hull, avstandspunkter
|
||||
for hull 1 -- IKKE for hull 2, bevisst, for å teste begge
|
||||
tilstander) og emulert GPS-posisjon (Chrome DevTools). Bekreftet:
|
||||
spillerkort synlig uten scrolling på mobilviewport (402px), "Vis
|
||||
banekart"-knappen vises for hull 1 og er HELT fraværende for hull 2,
|
||||
fanebytte-knappen veksler korrekt begge veier med riktig
|
||||
tekst/ikon, hele administrasjonspanelet (Flighter/Rediger runde/
|
||||
Fullfør/Slett/full spillerliste) fortsatt fullt funksjonelt etter
|
||||
fjerningen av den gamle faneraden. Ingen konsollfeil. `tsc --noEmit`
|
||||
rent, `vitest run` 55/55.
|
||||
|
||||
**Rullet ut 2026-08-20** -- bruker bekreftet. Ren frontend.
|
||||
`docker compose build teecup_frontend && up -d`, rene logger, 200 OK.
|
||||
|
|
|
|||
|
|
@ -187,11 +187,16 @@ export function HoleTargetDistance({
|
|||
holeNumber,
|
||||
size = "compact",
|
||||
className,
|
||||
onAvailabilityChange,
|
||||
}: {
|
||||
baseUrl: string
|
||||
holeNumber: number
|
||||
size?: "compact" | "full"
|
||||
className?: string
|
||||
/** Kalles hver gang det blir klart om banen faktisk har koordinatdata for
|
||||
* hullet eller ikke -- lar en kaller (f.eks. en utvid/skjul-knapp rundt
|
||||
* komponenten) vite om det er noe å vise, uten selv å duplisere fetch-en. */
|
||||
onAvailabilityChange?: (available: boolean) => void
|
||||
}) {
|
||||
const [points, setPoints] = useState<ApiTargetPoint[] | null>(null)
|
||||
const [livePosition, setLivePosition] = useState<LatLng | null>(null)
|
||||
|
|
@ -213,6 +218,12 @@ export function HoleTargetDistance({
|
|||
}
|
||||
}, [baseUrl, holeNumber])
|
||||
|
||||
useEffect(() => {
|
||||
if (points === null) return
|
||||
onAvailabilityChange?.(points.length > 0)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [points])
|
||||
|
||||
useEffect(() => {
|
||||
// Ingen vits å spore GPS når banen uansett ikke har koordinatdata for
|
||||
// dette hullet -- unngår unødvendig batteribruk/tillatelses-prompt.
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { RoundPageShell } from "@/components/round-page-shell"
|
|||
import { RoundMessages } from "@/components/round-messages"
|
||||
import type { FlagMapEntry } from "@/components/flag-map-overview"
|
||||
import {
|
||||
ArrowDown,
|
||||
ArrowRight,
|
||||
Check,
|
||||
ChevronDown,
|
||||
|
|
@ -1502,32 +1503,32 @@ export function RoundDetail({ roundId }: { roundId: string }) {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* To faner: "Score" (default, KUN hull-navigasjon + registrering --
|
||||
det eneste de fleste trenger midt i en runde) og "Spillere og
|
||||
runde" (alt administrativt, samlet ett sted i stedet for å ligge
|
||||
i veien øverst på scoringssiden). */}
|
||||
<div className="mb-5 inline-flex gap-1 rounded-full bg-muted p-1">
|
||||
{/* Ett enkelt, sekundært veksle-valg -- IKKE en fanerad (2026-08-20,
|
||||
reelt brukerfunn: en andre pille-fanerad her, rett under
|
||||
RoundHeader sin egen Score/Scorekort/Leaderboard-rad, leste som
|
||||
to duplikate "Score"-kontroller rett oppå hverandre). "Score"
|
||||
(hull-navigasjon + registrering, default) og "Spillere og runde"
|
||||
(alt administrativt) er fortsatt to distinkte visninger på denne
|
||||
siden -- kun selve KONTROLLEN er nå en enkelt, tydelig ANNERLEDES
|
||||
knapp (ramme + ikon, ikke en pille) i stedet for en segmentert
|
||||
fanerad som konkurrerer visuelt med hovedfanene over. */}
|
||||
<div className="mb-5 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPageTab("score")}
|
||||
aria-pressed={pageTab === "score"}
|
||||
className={cn(
|
||||
"min-h-11 rounded-full px-4 text-sm font-bold transition-colors",
|
||||
pageTab === "score" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
Score
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPageTab("manage")}
|
||||
aria-pressed={pageTab === "manage"}
|
||||
className={cn(
|
||||
"min-h-11 rounded-full px-4 text-sm font-bold transition-colors",
|
||||
pageTab === "manage" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:text-foreground",
|
||||
)}
|
||||
onClick={() => setPageTab(pageTab === "score" ? "manage" : "score")}
|
||||
className="flex h-11 items-center gap-2 rounded-xl border border-border bg-card px-3 text-sm font-bold text-foreground transition-colors hover:bg-accent/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
>
|
||||
{pageTab === "score" ? (
|
||||
<>
|
||||
<Settings2 aria-hidden="true" className="size-4 shrink-0 text-primary" />
|
||||
Spillere og runde
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ChevronLeft aria-hidden="true" className="size-4 shrink-0" />
|
||||
Tilbake til score
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -6616,6 +6617,14 @@ function PlayerHoleCards({
|
|||
// sitter her i kortet (skjermen FØR slagvinduet), åpner denne fullskjerm-
|
||||
// visningen ved klikk i stedet for å ekspandere inline.
|
||||
const [historyPlayerId, setHistoryPlayerId] = useState<string | null>(null)
|
||||
// Avstandsmåler-utvidelse (2026-08-20): kompakt som standard, slik at
|
||||
// spillerkortene under peker inn i synlig område med det samme -- en
|
||||
// førstegangsbruker skal ikke måtte oppdage ved et tilfeldig sveip at
|
||||
// score-registrering finnes lenger ned. "Vis banekart"-knappen vises kun
|
||||
// når banen faktisk HAR koordinatdata (ellers ingenting å utvide til).
|
||||
const [mapExpanded, setMapExpanded] = useState(false)
|
||||
const [distanceAvailable, setDistanceAvailable] = useState(false)
|
||||
const playerListRef = useRef<HTMLUListElement>(null)
|
||||
const holeMeta: Record<number, { par: number; index: number }> = {}
|
||||
for (const list of Object.values(holesByParticipant)) {
|
||||
for (const h of list) {
|
||||
|
|
@ -6719,10 +6728,45 @@ function PlayerHoleCards({
|
|||
{/* Avstand til grønn (ADR-064, utvidet til ekte teeoff-baner i
|
||||
migrasjon 079) -- kun for baner med koordinatdata; ingen synlig
|
||||
rad i det hele tatt for andre baner (HoleTargetDistance returnerer
|
||||
null da). Full bredde, samme plassering brukeren pekte ut (rett
|
||||
under hull-navigatoren, over spillerkortene) -- ikke den smale
|
||||
kompaktvarianten. */}
|
||||
<HoleTargetDistance baseUrl={`/rounds/${roundId}`} holeNumber={currentHole} size="full" />
|
||||
null da, "Vis banekart"-knappen skjules da også via
|
||||
distanceAvailable). Kompakt som standard (2026-08-20, reelt
|
||||
brukerfunn: det store diagrammet fylte hele skjermen på mobil, og
|
||||
ingenting antydet at spillerkortene under fantes) -- kan utvides
|
||||
til det fulle diagrammet med hindringer på forespørsel. */}
|
||||
<HoleTargetDistance
|
||||
baseUrl={`/rounds/${roundId}`}
|
||||
holeNumber={currentHole}
|
||||
size={mapExpanded ? "full" : "compact"}
|
||||
onAvailabilityChange={setDistanceAvailable}
|
||||
/>
|
||||
|
||||
{distanceAvailable && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMapExpanded((v) => !v)}
|
||||
aria-expanded={mapExpanded}
|
||||
className="flex h-11 items-center justify-center gap-2 self-center rounded-xl border border-border bg-card px-4 text-sm font-bold text-foreground transition-colors hover:bg-accent/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
>
|
||||
<MapPin aria-hidden="true" className="size-4 shrink-0 text-primary" />
|
||||
{mapExpanded ? "Skjul banekart" : "Vis banekart og hindringer"}
|
||||
<ChevronDown
|
||||
aria-hidden="true"
|
||||
className={cn("size-4 shrink-0 transition-transform", mapExpanded && "rotate-180")}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Alltid synlig, tydelig snarvei rett til score-registreringen under
|
||||
-- samme grunn som knappen over: en førstegangsbruker skal ikke
|
||||
måtte oppdage ved et tilfeldig sveip at dette finnes. */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => playerListRef.current?.scrollIntoView({ behavior: "smooth", block: "start" })}
|
||||
className="flex h-14 w-full items-center justify-center gap-2 rounded-2xl bg-primary text-base font-extrabold text-primary-foreground shadow-sm transition-colors hover:brightness-105 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
|
||||
>
|
||||
Registrer score
|
||||
<ArrowDown aria-hidden="true" className="size-5" />
|
||||
</button>
|
||||
|
||||
{isTwoSided && sides.length === 2 && (
|
||||
<div className="flex items-center justify-center gap-2 rounded-2xl border border-border bg-card px-4 py-2.5 text-sm">
|
||||
|
|
@ -6734,7 +6778,7 @@ function PlayerHoleCards({
|
|||
</div>
|
||||
)}
|
||||
|
||||
<ul className="flex flex-col gap-3">
|
||||
<ul ref={playerListRef} className="flex scroll-mt-4 flex-col gap-3">
|
||||
{sortedPlayers.map((player) => {
|
||||
const list = holesByParticipant[player.id]
|
||||
const apiHole = list?.find((h) => h.hole_number === currentHole) ?? null
|
||||
|
|
|
|||
Loading…
Reference in a new issue