Begge fiksene er ferdig og verifisert med ekte produksjonsbuild (ren frontend-endring, ingen migrasjon, ingen backend-endring):
«Ny organisasjon»-knapp i dashboardets header (ved siden av «Medlemmer»/«Ny turnering») — lar en bruker som allerede eier én org opprette en til, uten begrensning (backend støttet det allerede). Dato-sammendrag øverst på Program-skjermens øktliste — «X av Y runder har fått dato og klokkeslett» (grønn/uthevet når alle er satt).
This commit is contained in:
parent
3f888f1e10
commit
f4bce6a3d2
2 changed files with 125 additions and 17 deletions
|
|
@ -241,6 +241,7 @@ export function Dashboard() {
|
|||
loadingTournaments={loadingTournaments}
|
||||
onSelectOrg={setActiveOrgId}
|
||||
onCreateTournament={handleCreateTournament}
|
||||
onCreateOrg={handleCreateOrg}
|
||||
/>
|
||||
) : (
|
||||
<CreateOrganizationState onCreate={handleCreateOrg} compact={hasMyTournaments} />
|
||||
|
|
@ -393,6 +394,7 @@ function OrganizationView({
|
|||
loadingTournaments,
|
||||
onSelectOrg,
|
||||
onCreateTournament,
|
||||
onCreateOrg,
|
||||
}: {
|
||||
org: MyOrg
|
||||
orgs: MyOrg[]
|
||||
|
|
@ -400,6 +402,7 @@ function OrganizationView({
|
|||
loadingTournaments: boolean
|
||||
onSelectOrg: (id: string) => void
|
||||
onCreateTournament: (name: string) => void
|
||||
onCreateOrg: (name: string) => void
|
||||
}) {
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
|
|
@ -422,7 +425,7 @@ function OrganizationView({
|
|||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Link
|
||||
href={`/organizations/${org.organization_id}/members?name=${encodeURIComponent(org.name)}`}
|
||||
className="inline-flex h-11 items-center gap-1.5 rounded-2xl border border-border bg-card px-4 text-sm font-semibold text-foreground transition-colors hover:bg-accent/50"
|
||||
|
|
@ -430,6 +433,7 @@ function OrganizationView({
|
|||
<Users aria-hidden="true" className="size-4" />
|
||||
Medlemmer
|
||||
</Link>
|
||||
<NewOrganizationControl onCreate={onCreateOrg} />
|
||||
<NewTournamentControl onCreate={onCreateTournament} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -497,6 +501,75 @@ function OrganizationSwitcher({
|
|||
)
|
||||
}
|
||||
|
||||
// Ingen vei fantes tidligere til å opprette en ANDRE organisasjon når man
|
||||
// allerede har én -- CreateOrganizationState (under) vises kun ved null
|
||||
// org-er. Backend (POST /orgs) har ingen begrensning på antall org-er én
|
||||
// bruker kan eie (ADR-021), så dette var en ren UI-mangel.
|
||||
function NewOrganizationControl({ onCreate }: { onCreate: (name: string) => void }) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [name, setName] = useState("")
|
||||
const valid = name.trim().length >= 2
|
||||
|
||||
function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault()
|
||||
if (!valid) return
|
||||
onCreate(name.trim())
|
||||
setName("")
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
if (!open) {
|
||||
return (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setOpen(true)}
|
||||
className="inline-flex h-11 items-center gap-1.5 rounded-2xl border border-border bg-card px-4 text-sm font-semibold text-foreground transition-colors hover:bg-accent/50"
|
||||
>
|
||||
<Building2 aria-hidden="true" className="size-4" />
|
||||
Ny organisasjon
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex w-full items-center gap-2 rounded-2xl border border-border bg-card p-2 shadow-sm shadow-black/5 sm:w-auto"
|
||||
>
|
||||
<Input
|
||||
autoFocus
|
||||
placeholder="Navn på organisasjon"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") setOpen(false)
|
||||
}}
|
||||
className="h-10 min-w-0 flex-1 rounded-xl text-base sm:w-56"
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={!valid}
|
||||
size="icon"
|
||||
className="size-10 shrink-0 rounded-xl"
|
||||
aria-label="Opprett organisasjon"
|
||||
>
|
||||
<Check aria-hidden="true" className="size-5" />
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setOpen(false)}
|
||||
className="size-10 shrink-0 rounded-xl text-muted-foreground"
|
||||
aria-label="Avbryt"
|
||||
>
|
||||
<X aria-hidden="true" className="size-5" />
|
||||
</Button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
function NewTournamentControl({ onCreate }: { onCreate: (name: string) => void }) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [name, setName] = useState("")
|
||||
|
|
|
|||
|
|
@ -321,6 +321,11 @@ export function TournamentProgram({
|
|||
{sorted.length === 0 ? (
|
||||
<EmptyState />
|
||||
) : (
|
||||
<>
|
||||
<DateCoverageSummary
|
||||
total={sorted.length}
|
||||
withDate={sorted.filter((s) => s.scheduled_at !== null).length}
|
||||
/>
|
||||
<ol className="flex flex-col gap-3">
|
||||
{sorted.map((session, index) => (
|
||||
<SessionCard
|
||||
|
|
@ -337,6 +342,7 @@ export function TournamentProgram({
|
|||
/>
|
||||
))}
|
||||
</ol>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="mt-6">
|
||||
|
|
@ -844,6 +850,35 @@ function MetaBadge({ children }: { children: React.ReactNode }) {
|
|||
)
|
||||
}
|
||||
|
||||
// --- Date coverage summary ---------------------------------------------------
|
||||
// Ingen sammendrag fantes tidligere for "har alle runder fått dato" -- måtte
|
||||
// leses manuelt per øktkort. All nødvendig data finnes allerede i sessions.
|
||||
|
||||
function DateCoverageSummary({ total, withDate }: { total: number; withDate: number }) {
|
||||
const allSet = withDate === total
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"mb-3 flex items-center gap-2 rounded-2xl border px-4 py-3 text-sm font-semibold",
|
||||
allSet
|
||||
? "border-primary/30 bg-primary/5 text-primary"
|
||||
: "border-border bg-muted/50 text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{allSet ? (
|
||||
<Check aria-hidden="true" className="size-4 shrink-0" />
|
||||
) : (
|
||||
<Clock aria-hidden="true" className="size-4 shrink-0" />
|
||||
)}
|
||||
<span>
|
||||
{allSet
|
||||
? `Alle ${total} runder har fått dato og klokkeslett.`
|
||||
: `${withDate} av ${total} runder har fått dato og klokkeslett.`}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// --- Empty state -----------------------------------------------------------
|
||||
|
||||
function EmptyState() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue