Mitai analysis-split Sub-Nav und weniger Blend-UI bei Auspraegungen.
Some checks failed
Test Suite / lint-backend (push) Waiting to run
Test Suite / compose-smoke (push) Waiting to run
Test Suite / k6 /api/health Baseline (push) Blocked by required conditions
Test Suite / playwright-smoke (push) Blocked by required conditions
Deploy Development / deploy (push) Successful in 46s
Test Suite / pytest-backend (push) Has been cancelled
Some checks failed
Test Suite / lint-backend (push) Waiting to run
Test Suite / compose-smoke (push) Waiting to run
Test Suite / k6 /api/health Baseline (push) Blocked by required conditions
Test Suite / playwright-smoke (push) Blocked by required conditions
Deploy Development / deploy (push) Successful in 46s
Test Suite / pytest-backend (push) Has been cancelled
Modus-Subnav als linke Spalte (Plan/Kontrolle/Ausfuehren), Referenz-Auspraegungen aus Formularen entfernt, Hub-Karten auf Steuerung gestrichen. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
b0594df5f2
commit
5e2ae9de31
|
|
@ -8,10 +8,7 @@ import {
|
||||||
getInitiativeFields,
|
getInitiativeFields,
|
||||||
listArchetypeFieldDefinitions,
|
listArchetypeFieldDefinitions,
|
||||||
} from '../api/entityFields.js'
|
} from '../api/entityFields.js'
|
||||||
import { listSteeringMethods } from '../api/steering.js'
|
|
||||||
import { listMethodProfiles } from '../api/methodProfiles.js'
|
|
||||||
import { FieldRenderer } from './FieldRenderer.jsx'
|
import { FieldRenderer } from './FieldRenderer.jsx'
|
||||||
import { InitiativeSteeringMeta } from './InitiativeSteeringMeta.jsx'
|
|
||||||
|
|
||||||
export function InitiativeForm({
|
export function InitiativeForm({
|
||||||
initial = {},
|
initial = {},
|
||||||
|
|
@ -26,42 +23,6 @@ export function InitiativeForm({
|
||||||
const [fieldDefinitions, setFieldDefinitions] = useState([])
|
const [fieldDefinitions, setFieldDefinitions] = useState([])
|
||||||
const [dynamicValues, setDynamicValues] = useState({})
|
const [dynamicValues, setDynamicValues] = useState({})
|
||||||
const [fieldsLoading, setFieldsLoading] = useState(false)
|
const [fieldsLoading, setFieldsLoading] = useState(false)
|
||||||
const [steeringMethods, setSteeringMethods] = useState([])
|
|
||||||
const [methodProfiles, setMethodProfiles] = useState([])
|
|
||||||
const [methodProfileKey, setMethodProfileKey] = useState('')
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let cancelled = false
|
|
||||||
listSteeringMethods()
|
|
||||||
.then((items) => {
|
|
||||||
if (!cancelled) setSteeringMethods(Array.isArray(items) ? items : [])
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
if (!cancelled) setSteeringMethods([])
|
|
||||||
})
|
|
||||||
return () => {
|
|
||||||
cancelled = true
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let cancelled = false
|
|
||||||
setMethodProfileKey('')
|
|
||||||
if (!archetypeKey) {
|
|
||||||
setMethodProfiles([])
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
listMethodProfiles(archetypeKey)
|
|
||||||
.then((items) => {
|
|
||||||
if (!cancelled) setMethodProfiles(Array.isArray(items) ? items : [])
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
if (!cancelled) setMethodProfiles([])
|
|
||||||
})
|
|
||||||
return () => {
|
|
||||||
cancelled = true
|
|
||||||
}
|
|
||||||
}, [archetypeKey])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
|
|
@ -130,7 +91,6 @@ export function InitiativeForm({
|
||||||
status: form.status.value,
|
status: form.status.value,
|
||||||
priority: form.priority.value,
|
priority: form.priority.value,
|
||||||
dynamicFields: dynamicValues,
|
dynamicFields: dynamicValues,
|
||||||
...(methodProfileKey ? { method_profile_key: methodProfileKey } : {}),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,6 +99,7 @@ export function InitiativeForm({
|
||||||
}
|
}
|
||||||
|
|
||||||
const formReady = archetypes && !fieldsLoading
|
const formReady = archetypes && !fieldsLoading
|
||||||
|
const selectedArchetype = (archetypes || []).find((item) => item.key === archetypeKey)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="form workspace-form" onSubmit={handleSubmit}>
|
<form className="form workspace-form" onSubmit={handleSubmit}>
|
||||||
|
|
@ -160,31 +121,10 @@ export function InitiativeForm({
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
{selectedArchetype?.description && (
|
||||||
|
<span className="muted form-hint">{selectedArchetype.description}</span>
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
{methodProfiles.length > 0 && (
|
|
||||||
<label>
|
|
||||||
Ausprägung (optional)
|
|
||||||
<select
|
|
||||||
name="method_profile_key"
|
|
||||||
value={methodProfileKey}
|
|
||||||
onChange={(e) => setMethodProfileKey(e.target.value)}
|
|
||||||
>
|
|
||||||
<option value="">— Standard (nur Archetyp) —</option>
|
|
||||||
{methodProfiles.map((item) => (
|
|
||||||
<option key={item.key} value={item.key}>
|
|
||||||
{item.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
)}
|
|
||||||
<InitiativeSteeringMeta
|
|
||||||
archetypeKey={archetypeKey}
|
|
||||||
initiativeArchetypes={archetypes || []}
|
|
||||||
steeringMethods={steeringMethods}
|
|
||||||
methodProfiles={methodProfiles}
|
|
||||||
selectedMethodProfileKey={methodProfileKey}
|
|
||||||
/>
|
|
||||||
<label>
|
<label>
|
||||||
Kurzbeschreibung (Ziel)
|
Kurzbeschreibung (Ziel)
|
||||||
<textarea name="goal" rows={2} defaultValue={initial.goal || ''} />
|
<textarea name="goal" rows={2} defaultValue={initial.goal || ''} />
|
||||||
|
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
import { Link } from 'react-router-dom'
|
|
||||||
import { scopedPath } from '../utils/routes.js'
|
|
||||||
|
|
||||||
const AREAS = [
|
|
||||||
{
|
|
||||||
to: '/plan/gates',
|
|
||||||
title: 'Plan',
|
|
||||||
description: 'Roadmap, Gates und Plan-Ist — methodenneutral pflegen.',
|
|
||||||
countKey: 'roadmapItems',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: '/work/today',
|
|
||||||
title: 'Ausführung',
|
|
||||||
description: 'Arbeitspakete, Zuweisungen, Blocker am Objekt.',
|
|
||||||
countKey: 'actionsOpen',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: '/plan/inbox',
|
|
||||||
title: 'Eingang',
|
|
||||||
description: 'Backlog triagieren und in Arbeitspakete überführen.',
|
|
||||||
countKey: 'backlog',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: '/control/journey',
|
|
||||||
title: 'Nachvollziehbarkeit',
|
|
||||||
description: 'Entscheidungen, Reviews, Nachweise über die Zeit.',
|
|
||||||
countKey: null,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export function InitiativeNavCards({ initiativeId, counts = {} }) {
|
|
||||||
return (
|
|
||||||
<section className="initiative-nav-cards">
|
|
||||||
<h2 className="initiative-nav-cards-title">Pflege & Planung</h2>
|
|
||||||
<div className="initiative-nav-cards-grid">
|
|
||||||
{AREAS.map((area) => {
|
|
||||||
const hint =
|
|
||||||
area.countKey === 'backlog' && counts.backlog
|
|
||||||
? `${counts.backlog} Backlog`
|
|
||||||
: area.countKey === 'actionsOpen' && counts.actionsOpen
|
|
||||||
? `${counts.actionsOpen} offen`
|
|
||||||
: area.countKey === 'roadmapItems' && counts.roadmapItems
|
|
||||||
? `${counts.roadmapItems} Plan-Elemente`
|
|
||||||
: null
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
key={area.to}
|
|
||||||
to={scopedPath(area.to, { initiativeId })}
|
|
||||||
className="initiative-nav-card card"
|
|
||||||
>
|
|
||||||
<h3>{area.title}</h3>
|
|
||||||
<p className="muted">{area.description}</p>
|
|
||||||
{hint && <span className="initiative-nav-card-hint">{hint}</span>}
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -3,38 +3,21 @@ import {
|
||||||
findArchetype,
|
findArchetype,
|
||||||
methodDescriptionForKey,
|
methodDescriptionForKey,
|
||||||
methodLabelForKey,
|
methodLabelForKey,
|
||||||
methodProfileDescription,
|
|
||||||
methodProfileLabel,
|
|
||||||
} from '../utils/archetypes.js'
|
} from '../utils/archetypes.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zeigt Archetyp → Methode → Ausprägung transparent (Capture & Lagebild).
|
* Archetyp und abgeleitete Steuerungsmethode (read-only Kontext).
|
||||||
*/
|
*/
|
||||||
export function InitiativeSteeringMeta({
|
export function InitiativeSteeringMeta({
|
||||||
archetypeKey,
|
archetypeKey,
|
||||||
initiativeArchetypes = [],
|
initiativeArchetypes = [],
|
||||||
steeringSnapshot = null,
|
steeringSnapshot = null,
|
||||||
steeringMethods = [],
|
steeringMethods = [],
|
||||||
methodProfiles = [],
|
|
||||||
selectedMethodProfileKey = '',
|
|
||||||
compact = false,
|
compact = false,
|
||||||
}) {
|
}) {
|
||||||
const archetype = findArchetype(archetypeKey, initiativeArchetypes)
|
const archetype = findArchetype(archetypeKey, initiativeArchetypes)
|
||||||
const profileKey =
|
|
||||||
steeringSnapshot?.method_profile_key || selectedMethodProfileKey || null
|
|
||||||
const profileLabel =
|
|
||||||
steeringSnapshot?.method_profile_label ||
|
|
||||||
methodProfileLabel(profileKey, methodProfiles)
|
|
||||||
const profileDescription = methodProfileDescription(profileKey, methodProfiles)
|
|
||||||
|
|
||||||
const selectedProfile = profileKey
|
|
||||||
? methodProfiles.find((item) => item.key === profileKey)
|
|
||||||
: null
|
|
||||||
const methodKey =
|
const methodKey =
|
||||||
steeringSnapshot?.method_key ||
|
steeringSnapshot?.method_key || archetype?.default_method_key || null
|
||||||
selectedProfile?.method_key ||
|
|
||||||
archetype?.default_method_key ||
|
|
||||||
null
|
|
||||||
const methodLabel =
|
const methodLabel =
|
||||||
steeringSnapshot?.method_label || methodLabelForKey(methodKey, steeringMethods)
|
steeringSnapshot?.method_label || methodLabelForKey(methodKey, steeringMethods)
|
||||||
const methodDescription = methodDescriptionForKey(methodKey, steeringMethods)
|
const methodDescription = methodDescriptionForKey(methodKey, steeringMethods)
|
||||||
|
|
@ -62,19 +45,6 @@ export function InitiativeSteeringMeta({
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{profileKey && (
|
|
||||||
<div className="initiative-steering-meta__row">
|
|
||||||
<span className="initiative-steering-meta__label">Ausprägung</span>
|
|
||||||
<span className="initiative-steering-meta__value">
|
|
||||||
<strong>{profileLabel}</strong>
|
|
||||||
{!compact && profileDescription && (
|
|
||||||
<span className="initiative-steering-meta__hint muted">
|
|
||||||
{profileDescription}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{methodKey && (
|
{methodKey && (
|
||||||
<div className="initiative-steering-meta__row">
|
<div className="initiative-steering-meta__row">
|
||||||
<span className="initiative-steering-meta__label">Steuerungsmethode</span>
|
<span className="initiative-steering-meta__label">Steuerungsmethode</span>
|
||||||
|
|
@ -85,7 +55,7 @@ export function InitiativeSteeringMeta({
|
||||||
{methodDescription}
|
{methodDescription}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{!compact && !steeringSnapshot && archetype?.default_method_key && !profileKey && (
|
{!compact && !steeringSnapshot && archetype?.default_method_key && (
|
||||||
<span className="initiative-steering-meta__hint muted">
|
<span className="initiative-steering-meta__hint muted">
|
||||||
Wird beim Anlegen automatisch vom Archetyp abgeleitet.
|
Wird beim Anlegen automatisch vom Archetyp abgeleitet.
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { listMethodProfiles } from '../api/methodProfiles.js'
|
|
||||||
|
|
||||||
export function MethodProfileSelect({
|
|
||||||
archetypeKey,
|
|
||||||
value = '',
|
|
||||||
onChange,
|
|
||||||
disabled = false,
|
|
||||||
}) {
|
|
||||||
const [profiles, setProfiles] = useState([])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!archetypeKey) {
|
|
||||||
setProfiles([])
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
let cancelled = false
|
|
||||||
listMethodProfiles(archetypeKey)
|
|
||||||
.then((items) => {
|
|
||||||
if (!cancelled) setProfiles(Array.isArray(items) ? items : [])
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
if (!cancelled) setProfiles([])
|
|
||||||
})
|
|
||||||
return () => {
|
|
||||||
cancelled = true
|
|
||||||
}
|
|
||||||
}, [archetypeKey])
|
|
||||||
|
|
||||||
if (profiles.length === 0) {
|
|
||||||
return (
|
|
||||||
<p className="muted method-profile-select__empty">
|
|
||||||
Keine Referenz-Ausprägung für diesen Archetyp hinterlegt.
|
|
||||||
</p>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<label className="method-profile-select">
|
|
||||||
Ausprägung
|
|
||||||
<select
|
|
||||||
value={value || ''}
|
|
||||||
disabled={disabled}
|
|
||||||
onChange={(e) => onChange?.(e.target.value)}
|
|
||||||
aria-label="Methoden-Ausprägung"
|
|
||||||
>
|
|
||||||
<option value="">— Standard (nur Archetyp) —</option>
|
|
||||||
{profiles.map((item) => (
|
|
||||||
<option key={item.key} value={item.key}>
|
|
||||||
{item.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
63
frontend/src/components/ModeAreaNav.jsx
Normal file
63
frontend/src/components/ModeAreaNav.jsx
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { NavLink, useLocation } from 'react-router-dom'
|
||||||
|
import { useProgramScope } from '../context/ProgramScopeContext.jsx'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bereichs-Subnav (Mitai Settings/Admin-Shell).
|
||||||
|
*
|
||||||
|
* @param {{
|
||||||
|
* modeLabel: string,
|
||||||
|
* items: { key: string, to: string, label: string, labelFor?: (ctx: object) => string }[],
|
||||||
|
* resolveActiveKey?: (pathname: string) => string | null,
|
||||||
|
* showScope?: boolean,
|
||||||
|
* scopeMutedLabel?: string,
|
||||||
|
* }} props
|
||||||
|
*/
|
||||||
|
export function ModeAreaNav({
|
||||||
|
modeLabel,
|
||||||
|
items,
|
||||||
|
resolveActiveKey,
|
||||||
|
showScope = false,
|
||||||
|
scopeMutedLabel = 'Portfolio',
|
||||||
|
}) {
|
||||||
|
const location = useLocation()
|
||||||
|
const { initiativeId, initiativeTitle, hrefWithScope } = useProgramScope()
|
||||||
|
const activeKey = resolveActiveKey?.(location.pathname)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className="analysis-split__nav" aria-label={`${modeLabel}-Navigation`}>
|
||||||
|
<div className="analysis-split__nav-header">
|
||||||
|
<span className="analysis-split__nav-eyebrow">{modeLabel}</span>
|
||||||
|
{showScope &&
|
||||||
|
(initiativeId ? (
|
||||||
|
<p className="analysis-split__nav-scope" title={initiativeTitle || initiativeId}>
|
||||||
|
{initiativeTitle || 'Vorhaben'}
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<p className="analysis-split__nav-scope analysis-split__nav-scope--muted">
|
||||||
|
{scopeMutedLabel}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<ul className="analysis-split__nav-list">
|
||||||
|
{items.map((item) => {
|
||||||
|
const label = item.labelFor ? item.labelFor({}) : item.label
|
||||||
|
const isActive = activeKey === item.key
|
||||||
|
return (
|
||||||
|
<li key={item.key}>
|
||||||
|
<NavLink
|
||||||
|
to={hrefWithScope(item.to)}
|
||||||
|
end
|
||||||
|
className={({ isActive: routerActive }) =>
|
||||||
|
'analysis-split__nav-link' +
|
||||||
|
(routerActive || isActive ? ' analysis-split__nav-link--active' : '')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
||||||
11
frontend/src/components/ModeAreaShell.jsx
Normal file
11
frontend/src/components/ModeAreaShell.jsx
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
/**
|
||||||
|
* Mitai analysis-split: linke Sub-Nav + Hauptinhalt (Desktop), gestapelt (Mobile).
|
||||||
|
*/
|
||||||
|
export function ModeAreaShell({ nav, children }) {
|
||||||
|
return (
|
||||||
|
<div className="analysis-split">
|
||||||
|
{nav}
|
||||||
|
<div className="analysis-split__main">{children}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import { NavLink } from 'react-router-dom'
|
|
||||||
import { useProgramScope } from '../context/ProgramScopeContext.jsx'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {{ items: { to: string, label: string, end?: boolean }[] }} props
|
|
||||||
*/
|
|
||||||
export function ModeSubNav({ items }) {
|
|
||||||
const { hrefWithScope } = useProgramScope()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<nav className="mode-subnav" aria-label="Modus-Unternavigation">
|
|
||||||
{items.map((item) => (
|
|
||||||
<NavLink
|
|
||||||
key={item.to}
|
|
||||||
to={hrefWithScope(item.to)}
|
|
||||||
end={item.end !== false}
|
|
||||||
className={({ isActive }) =>
|
|
||||||
'mode-subnav__link' + (isActive ? ' mode-subnav__link--active' : '')
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{item.label}
|
|
||||||
</NavLink>
|
|
||||||
))}
|
|
||||||
</nav>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
import { NavLink, useLocation } from 'react-router-dom'
|
import { NavLink, useLocation } from 'react-router-dom'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { getInitiative, listInitiativeActions } from '../api/initiatives.js'
|
import { listInitiativeActions } from '../api/initiatives.js'
|
||||||
import { listInitiativeBacklog } from '../api/backlog.js'
|
import { listInitiativeBacklog } from '../api/backlog.js'
|
||||||
import { useProgramScope } from '../context/ProgramScopeContext.jsx'
|
import { useProgramScope } from '../context/ProgramScopeContext.jsx'
|
||||||
import { useEntityArchetypes } from '../hooks/useEntityArchetypes.js'
|
|
||||||
import { InitiativeSteeringMeta } from './InitiativeSteeringMeta.jsx'
|
|
||||||
import {
|
import {
|
||||||
PLAN_OUTLINE_NODES,
|
PLAN_OUTLINE_NODES,
|
||||||
resolvePlanOutlineActiveKey,
|
resolvePlanOutlineActiveKey,
|
||||||
|
|
@ -13,28 +11,8 @@ import {
|
||||||
export function PlanOutlineNav() {
|
export function PlanOutlineNav() {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const { initiativeId, initiativeTitle, hrefWithScope } = useProgramScope()
|
const { initiativeId, initiativeTitle, hrefWithScope } = useProgramScope()
|
||||||
const { initiativeArchetypes } = useEntityArchetypes()
|
|
||||||
const activeKey = resolvePlanOutlineActiveKey(location.pathname)
|
const activeKey = resolvePlanOutlineActiveKey(location.pathname)
|
||||||
const [counts, setCounts] = useState({ inbox: null, work: null })
|
const [counts, setCounts] = useState({ inbox: null, work: null })
|
||||||
const [initiativeArchetypeKey, setInitiativeArchetypeKey] = useState('')
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!initiativeId) {
|
|
||||||
setInitiativeArchetypeKey('')
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
let cancelled = false
|
|
||||||
getInitiative(initiativeId)
|
|
||||||
.then((data) => {
|
|
||||||
if (!cancelled) setInitiativeArchetypeKey(data.archetype_key || '')
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
if (!cancelled) setInitiativeArchetypeKey('')
|
|
||||||
})
|
|
||||||
return () => {
|
|
||||||
cancelled = true
|
|
||||||
}
|
|
||||||
}, [initiativeId])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!initiativeId) {
|
if (!initiativeId) {
|
||||||
|
|
@ -70,29 +48,21 @@ export function PlanOutlineNav() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="plan-outline-nav" aria-label="Plan-Outline">
|
<nav className="analysis-split__nav" aria-label="Plan-Navigation">
|
||||||
<div className="plan-outline-nav__header">
|
<div className="analysis-split__nav-header">
|
||||||
<span className="plan-outline-nav__eyebrow">Planen</span>
|
<span className="analysis-split__nav-eyebrow">Planen</span>
|
||||||
{initiativeId ? (
|
{initiativeId ? (
|
||||||
<p className="plan-outline-nav__root" title={initiativeTitle || initiativeId}>
|
<p className="analysis-split__nav-scope" title={initiativeTitle || initiativeId}>
|
||||||
{initiativeTitle || 'Vorhaben'}
|
{initiativeTitle || 'Vorhaben'}
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<p className="plan-outline-nav__root plan-outline-nav__root--muted">
|
<p className="analysis-split__nav-scope analysis-split__nav-scope--muted">
|
||||||
Portfolio — Vorhaben wählen
|
Portfolio — Vorhaben wählen
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{initiativeId && initiativeArchetypeKey && (
|
<ul className="analysis-split__nav-list">
|
||||||
<InitiativeSteeringMeta
|
|
||||||
archetypeKey={initiativeArchetypeKey}
|
|
||||||
initiativeArchetypes={initiativeArchetypes}
|
|
||||||
compact
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<ul className="plan-outline-nav__list">
|
|
||||||
{PLAN_OUTLINE_NODES.map((node) => {
|
{PLAN_OUTLINE_NODES.map((node) => {
|
||||||
const needsInitiative = node.requiresInitiative && !initiativeId
|
const needsInitiative = node.requiresInitiative && !initiativeId
|
||||||
const isDisabled = node.disabled || needsInitiative
|
const isDisabled = node.disabled || needsInitiative
|
||||||
|
|
@ -102,8 +72,8 @@ export function PlanOutlineNav() {
|
||||||
<li key={node.key}>
|
<li key={node.key}>
|
||||||
<span
|
<span
|
||||||
className={
|
className={
|
||||||
'plan-outline-nav__link plan-outline-nav__link--disabled' +
|
'analysis-split__nav-link analysis-split__nav-link--disabled' +
|
||||||
(activeKey === node.key ? ' plan-outline-nav__link--active' : '')
|
(activeKey === node.key ? ' analysis-split__nav-link--active' : '')
|
||||||
}
|
}
|
||||||
aria-disabled="true"
|
aria-disabled="true"
|
||||||
title={needsInitiative ? 'Vorhaben im Scope wählen' : node.hint}
|
title={needsInitiative ? 'Vorhaben im Scope wählen' : node.hint}
|
||||||
|
|
@ -120,8 +90,8 @@ export function PlanOutlineNav() {
|
||||||
to={hrefWithScope(node.to)}
|
to={hrefWithScope(node.to)}
|
||||||
end
|
end
|
||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
'plan-outline-nav__link' +
|
'analysis-split__nav-link' +
|
||||||
(isActive ? ' plan-outline-nav__link--active' : '')
|
(isActive ? ' analysis-split__nav-link--active' : '')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{nodeLabel(node)}
|
{nodeLabel(node)}
|
||||||
|
|
|
||||||
|
|
@ -54,8 +54,6 @@ export function SteeringSnapshotPanel({
|
||||||
method_label,
|
method_label,
|
||||||
archetype_key,
|
archetype_key,
|
||||||
archetype_label,
|
archetype_label,
|
||||||
method_profile_key,
|
|
||||||
method_profile_label,
|
|
||||||
steering_guidance,
|
steering_guidance,
|
||||||
attention_items = [],
|
attention_items = [],
|
||||||
signals = [],
|
signals = [],
|
||||||
|
|
@ -117,13 +115,6 @@ export function SteeringSnapshotPanel({
|
||||||
{(archetype_label || archetype_key) && (
|
{(archetype_label || archetype_key) && (
|
||||||
<p className="steering-archetype-row muted">
|
<p className="steering-archetype-row muted">
|
||||||
Archetyp: <strong>{archetype_label || archetype_key}</strong>
|
Archetyp: <strong>{archetype_label || archetype_key}</strong>
|
||||||
{method_profile_label || method_profile_key ? (
|
|
||||||
<>
|
|
||||||
{' '}
|
|
||||||
· Ausprägung:{' '}
|
|
||||||
<strong>{method_profile_label || method_profile_key}</strong>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{(method_label || canManageMethod) && (
|
{(method_label || canManageMethod) && (
|
||||||
|
|
|
||||||
24
frontend/src/config/controlNav.js
Normal file
24
frontend/src/config/controlNav.js
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
/**
|
||||||
|
* Kontrolle-Subnav (Mitai: settingsNav / analysis-split).
|
||||||
|
* Pfade müssen mit Routes in App.jsx übereinstimmen.
|
||||||
|
*
|
||||||
|
* @typedef {{ key: string, to: string, label: string }} ModeNavItem
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @type {ModeNavItem[]} */
|
||||||
|
export const CONTROL_NAV_ITEMS = [
|
||||||
|
{ key: 'status', to: '/control/status', label: 'Steuerung' },
|
||||||
|
{ key: 'journey', to: '/control/journey', label: 'Nachvollziehbarkeit' },
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} pathname
|
||||||
|
* @returns {string | null}
|
||||||
|
*/
|
||||||
|
export function resolveControlNavActiveKey(pathname) {
|
||||||
|
const path = pathname.replace(/\/$/, '') || pathname
|
||||||
|
const match = CONTROL_NAV_ITEMS.find(
|
||||||
|
(item) => path === item.to || path.startsWith(`${item.to}/`),
|
||||||
|
)
|
||||||
|
return match?.key ?? null
|
||||||
|
}
|
||||||
18
frontend/src/config/modeNav.test.js
Normal file
18
frontend/src/config/modeNav.test.js
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { resolveControlNavActiveKey } from '../config/controlNav.js'
|
||||||
|
import { resolveWorkNavActiveKey } from '../config/workNav.js'
|
||||||
|
|
||||||
|
describe('controlNav', () => {
|
||||||
|
it('resolves active control sub-route', () => {
|
||||||
|
expect(resolveControlNavActiveKey('/control/status')).toBe('status')
|
||||||
|
expect(resolveControlNavActiveKey('/control/journey')).toBe('journey')
|
||||||
|
expect(resolveControlNavActiveKey('/control')).toBe(null)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('workNav', () => {
|
||||||
|
it('resolves active work sub-route', () => {
|
||||||
|
expect(resolveWorkNavActiveKey('/work/today')).toBe('today')
|
||||||
|
expect(resolveWorkNavActiveKey('/work/mine')).toBe('mine')
|
||||||
|
})
|
||||||
|
})
|
||||||
24
frontend/src/config/workNav.js
Normal file
24
frontend/src/config/workNav.js
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
/**
|
||||||
|
* Ausführen-Subnav (Mitai: settingsNav / analysis-split).
|
||||||
|
* Pfade müssen mit Routes in App.jsx übereinstimmen.
|
||||||
|
*
|
||||||
|
* @typedef {{ key: string, to: string, label: string }} ModeNavItem
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @type {ModeNavItem[]} */
|
||||||
|
export const WORK_NAV_ITEMS = [
|
||||||
|
{ key: 'today', to: '/work/today', label: 'Heute' },
|
||||||
|
{ key: 'mine', to: '/work/mine', label: 'Meine Queue' },
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} pathname
|
||||||
|
* @returns {string | null}
|
||||||
|
*/
|
||||||
|
export function resolveWorkNavActiveKey(pathname) {
|
||||||
|
const path = pathname.replace(/\/$/, '') || pathname
|
||||||
|
const match = WORK_NAV_ITEMS.find(
|
||||||
|
(item) => path === item.to || path.startsWith(`${item.to}/`),
|
||||||
|
)
|
||||||
|
return match?.key ?? null
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,6 @@ import { scopedPath } from '../../utils/routes.js'
|
||||||
import { useInitiativeOperations } from '../../context/InitiativeOperationsContext.jsx'
|
import { useInitiativeOperations } from '../../context/InitiativeOperationsContext.jsx'
|
||||||
import { SteeringSnapshotPanel } from '../../components/SteeringSnapshotPanel.jsx'
|
import { SteeringSnapshotPanel } from '../../components/SteeringSnapshotPanel.jsx'
|
||||||
import { NextActionWidget } from '../../widgets/NextActionWidget.jsx'
|
import { NextActionWidget } from '../../widgets/NextActionWidget.jsx'
|
||||||
import { InitiativeNavCards } from '../../components/InitiativeNavCards.jsx'
|
|
||||||
|
|
||||||
export function InitiativeOverviewPage() {
|
export function InitiativeOverviewPage() {
|
||||||
const {
|
const {
|
||||||
|
|
@ -16,9 +15,6 @@ export function InitiativeOverviewPage() {
|
||||||
methodBusy,
|
methodBusy,
|
||||||
capabilities,
|
capabilities,
|
||||||
handleMethodChange,
|
handleMethodChange,
|
||||||
backlogItems,
|
|
||||||
roadmapItems,
|
|
||||||
milestones,
|
|
||||||
} = useInitiativeOperations()
|
} = useInitiativeOperations()
|
||||||
|
|
||||||
const counts = steeringSnapshot?.counts || {}
|
const counts = steeringSnapshot?.counts || {}
|
||||||
|
|
@ -66,15 +62,6 @@ export function InitiativeOverviewPage() {
|
||||||
</Link>
|
</Link>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<InitiativeNavCards
|
|
||||||
initiativeId={initiativeId}
|
|
||||||
counts={{
|
|
||||||
backlog: backlogItems.length,
|
|
||||||
roadmapItems: roadmapItems.length,
|
|
||||||
actionsOpen: counts.actions_open,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,29 @@
|
||||||
import { Navigate, Outlet } from 'react-router-dom'
|
import { Navigate, Outlet } from 'react-router-dom'
|
||||||
|
import { ModeAreaShell } from '../../components/ModeAreaShell.jsx'
|
||||||
|
import { ModeAreaNav } from '../../components/ModeAreaNav.jsx'
|
||||||
import { ModeShell } from '../../components/ModeShell.jsx'
|
import { ModeShell } from '../../components/ModeShell.jsx'
|
||||||
import { ModeSubNav } from '../../components/ModeSubNav.jsx'
|
import {
|
||||||
|
CONTROL_NAV_ITEMS,
|
||||||
const CONTROL_NAV = [
|
resolveControlNavActiveKey,
|
||||||
{ to: '/control/status', label: 'Steuerung', end: true },
|
} from '../../config/controlNav.js'
|
||||||
{ to: '/control/journey', label: 'Journey', end: true },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function ControlLayout() {
|
export function ControlLayout() {
|
||||||
return (
|
return (
|
||||||
<div className="app-page">
|
<div className="app-page">
|
||||||
<ModeShell modeKey="control" />
|
<ModeShell modeKey="control" />
|
||||||
<ModeSubNav items={CONTROL_NAV} />
|
<ModeAreaShell
|
||||||
<Outlet />
|
nav={
|
||||||
|
<ModeAreaNav
|
||||||
|
modeLabel="Kontrolle"
|
||||||
|
items={CONTROL_NAV_ITEMS}
|
||||||
|
resolveActiveKey={resolveControlNavActiveKey}
|
||||||
|
showScope
|
||||||
|
scopeMutedLabel="Vorhaben wählen"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Outlet />
|
||||||
|
</ModeAreaShell>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
import { Outlet } from 'react-router-dom'
|
import { Outlet } from 'react-router-dom'
|
||||||
|
import { ModeAreaShell } from '../../components/ModeAreaShell.jsx'
|
||||||
import { ModeShell } from '../../components/ModeShell.jsx'
|
import { ModeShell } from '../../components/ModeShell.jsx'
|
||||||
import { PlanOutlineNav } from '../../components/PlanOutlineNav.jsx'
|
import { PlanOutlineNav } from '../../components/PlanOutlineNav.jsx'
|
||||||
|
|
||||||
export function PlanLayout() {
|
export function PlanLayout() {
|
||||||
return (
|
return (
|
||||||
<div className="app-page plan-outline-shell">
|
<div className="app-page">
|
||||||
<ModeShell modeKey="plan" />
|
<ModeShell modeKey="plan" />
|
||||||
<div className="plan-outline-shell__body">
|
<ModeAreaShell nav={<PlanOutlineNav />}>
|
||||||
<PlanOutlineNav />
|
<Outlet />
|
||||||
<div className="plan-outline-shell__content">
|
</ModeAreaShell>
|
||||||
<Outlet />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import { LoadingState } from '../../components/LoadingState.jsx'
|
||||||
import { Modal } from '../../components/Modal.jsx'
|
import { Modal } from '../../components/Modal.jsx'
|
||||||
import { InitiativeForm } from '../../components/InitiativeForm.jsx'
|
import { InitiativeForm } from '../../components/InitiativeForm.jsx'
|
||||||
import { InitiativeSteeringMeta } from '../../components/InitiativeSteeringMeta.jsx'
|
import { InitiativeSteeringMeta } from '../../components/InitiativeSteeringMeta.jsx'
|
||||||
import { MethodProfileSelect } from '../../components/MethodProfileSelect.jsx'
|
|
||||||
import { PriorityBadge } from '../../components/PriorityBadge.jsx'
|
import { PriorityBadge } from '../../components/PriorityBadge.jsx'
|
||||||
import { useInitiativeOperations } from '../../context/InitiativeOperationsContext.jsx'
|
import { useInitiativeOperations } from '../../context/InitiativeOperationsContext.jsx'
|
||||||
import { useProgramScope } from '../../context/ProgramScopeContext.jsx'
|
import { useProgramScope } from '../../context/ProgramScopeContext.jsx'
|
||||||
|
|
@ -88,8 +87,6 @@ function PlanProfileInner() {
|
||||||
formBusy,
|
formBusy,
|
||||||
steeringSnapshot,
|
steeringSnapshot,
|
||||||
steeringMethods,
|
steeringMethods,
|
||||||
methodBusy,
|
|
||||||
handleMethodProfileChange,
|
|
||||||
actors,
|
actors,
|
||||||
projects,
|
projects,
|
||||||
} = ops
|
} = ops
|
||||||
|
|
@ -143,16 +140,6 @@ function PlanProfileInner() {
|
||||||
steeringSnapshot={steeringSnapshot}
|
steeringSnapshot={steeringSnapshot}
|
||||||
steeringMethods={steeringMethods}
|
steeringMethods={steeringMethods}
|
||||||
/>
|
/>
|
||||||
{canManage && (
|
|
||||||
<div className="plan-profile__steering-edit">
|
|
||||||
<MethodProfileSelect
|
|
||||||
archetypeKey={initiative.archetype_key}
|
|
||||||
value={steeringSnapshot?.method_profile_key || ''}
|
|
||||||
disabled={methodBusy}
|
|
||||||
onChange={handleMethodProfileChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="plan-profile__field">
|
<div className="plan-profile__field">
|
||||||
<h3 className="plan-profile__label">Verantwortlich (Owner)</h3>
|
<h3 className="plan-profile__label">Verantwortlich (Owner)</h3>
|
||||||
<p>{ownerName}</p>
|
<p>{ownerName}</p>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,24 @@
|
||||||
import { Navigate, Outlet } from 'react-router-dom'
|
import { Navigate, Outlet } from 'react-router-dom'
|
||||||
|
import { ModeAreaShell } from '../../components/ModeAreaShell.jsx'
|
||||||
|
import { ModeAreaNav } from '../../components/ModeAreaNav.jsx'
|
||||||
import { ModeShell } from '../../components/ModeShell.jsx'
|
import { ModeShell } from '../../components/ModeShell.jsx'
|
||||||
import { ModeSubNav } from '../../components/ModeSubNav.jsx'
|
import { WORK_NAV_ITEMS, resolveWorkNavActiveKey } from '../../config/workNav.js'
|
||||||
|
|
||||||
const WORK_NAV = [
|
|
||||||
{ to: '/work/today', label: 'Heute', end: true },
|
|
||||||
{ to: '/work/mine', label: 'Meine Queue', end: true },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function WorkLayout() {
|
export function WorkLayout() {
|
||||||
return (
|
return (
|
||||||
<div className="app-page">
|
<div className="app-page">
|
||||||
<ModeShell modeKey="work" />
|
<ModeShell modeKey="work" />
|
||||||
<ModeSubNav items={WORK_NAV} />
|
<ModeAreaShell
|
||||||
<Outlet />
|
nav={
|
||||||
|
<ModeAreaNav
|
||||||
|
modeLabel="Ausführen"
|
||||||
|
items={WORK_NAV_ITEMS}
|
||||||
|
resolveActiveKey={resolveWorkNavActiveKey}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Outlet />
|
||||||
|
</ModeAreaShell>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -223,28 +223,110 @@
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mode-subnav {
|
/* Mitai analysis-split: Bereichs-Shell mit linker Sub-Nav (Desktop) */
|
||||||
display: flex;
|
.analysis-split {
|
||||||
flex-wrap: wrap;
|
display: grid;
|
||||||
gap: 8px;
|
grid-template-columns: minmax(200px, 240px) minmax(0, 1fr);
|
||||||
margin-bottom: 16px;
|
gap: 20px;
|
||||||
|
align-items: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mode-subnav__link {
|
.analysis-split__main {
|
||||||
padding: 8px 14px;
|
min-width: 0;
|
||||||
border-radius: 999px;
|
}
|
||||||
text-decoration: none;
|
|
||||||
font-size: 13px;
|
.analysis-split__nav {
|
||||||
font-weight: 600;
|
position: sticky;
|
||||||
color: var(--jk-text-muted);
|
top: 12px;
|
||||||
|
padding: 14px;
|
||||||
|
border-radius: 12px;
|
||||||
border: 1px solid var(--jk-border);
|
border: 1px solid var(--jk-border);
|
||||||
background: var(--jk-surface);
|
background: var(--jk-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mode-subnav__link--active {
|
.analysis-split__nav-header {
|
||||||
color: var(--jk-primary);
|
margin-bottom: 12px;
|
||||||
border-color: var(--jk-primary);
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px solid var(--jk-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysis-split__nav-eyebrow {
|
||||||
|
display: block;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--jk-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysis-split__nav-scope {
|
||||||
|
margin: 6px 0 0;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.3;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysis-split__nav-scope--muted {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--jk-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysis-split__nav-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysis-split__nav-link {
|
||||||
|
display: block;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--jk-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysis-split__nav-link:hover {
|
||||||
background: var(--jk-primary-soft);
|
background: var(--jk-primary-soft);
|
||||||
|
color: var(--jk-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysis-split__nav-link--active {
|
||||||
|
color: var(--jk-primary);
|
||||||
|
background: var(--jk-primary-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysis-split__nav-link--disabled {
|
||||||
|
opacity: 0.55;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.analysis-split {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysis-split__nav-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysis-split__nav-link {
|
||||||
|
padding: 8px 14px;
|
||||||
|
border: 1px solid var(--jk-border);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--jk-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysis-split__nav-link--active {
|
||||||
|
border-color: var(--jk-primary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.scope-picker__list {
|
.scope-picker__list {
|
||||||
|
|
@ -292,93 +374,6 @@
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plan-outline-shell__body {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(200px, 240px) minmax(0, 1fr);
|
|
||||||
gap: 20px;
|
|
||||||
align-items: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
|
||||||
.plan-outline-shell__body {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.plan-outline-nav {
|
|
||||||
position: sticky;
|
|
||||||
top: 12px;
|
|
||||||
padding: 14px;
|
|
||||||
border-radius: 12px;
|
|
||||||
border: 1px solid var(--jk-border);
|
|
||||||
background: var(--jk-surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
.plan-outline-nav__header {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
border-bottom: 1px solid var(--jk-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.plan-outline-nav__eyebrow {
|
|
||||||
display: block;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: var(--jk-text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.plan-outline-nav__root {
|
|
||||||
margin: 6px 0 0;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 1.3;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.plan-outline-nav__root--muted {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--jk-text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.plan-outline-nav__list {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
display: grid;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.plan-outline-nav__link {
|
|
||||||
display: block;
|
|
||||||
padding: 8px 10px;
|
|
||||||
border-radius: 8px;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--jk-text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.plan-outline-nav__link:hover {
|
|
||||||
background: var(--jk-primary-soft);
|
|
||||||
color: var(--jk-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.plan-outline-nav__link--active {
|
|
||||||
color: var(--jk-primary);
|
|
||||||
background: var(--jk-primary-soft);
|
|
||||||
}
|
|
||||||
|
|
||||||
.plan-outline-nav__link--disabled {
|
|
||||||
opacity: 0.55;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.plan-outline-shell__content {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.plan-profile__header {
|
.plan-profile__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user