AP1.12a: Plan-Outline-Shell ersetzt Untertabs im Planen-Modus.
All checks were successful
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Successful in 2m4s
Test Suite / lint-backend (push) Successful in 2s
Test Suite / compose-smoke (push) Has been skipped
Test Suite / k6 /api/health Baseline (push) Successful in 18s
Test Suite / playwright-smoke (push) Successful in 13s
All checks were successful
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Successful in 2m4s
Test Suite / lint-backend (push) Successful in 2s
Test Suite / compose-smoke (push) Has been skipped
Test Suite / k6 /api/health Baseline (push) Successful in 18s
Test Suite / playwright-smoke (push) Successful in 13s
Linke Outline mit Profil, Struktur, Zielzuständen und Eingang; Portfolio-Einstieg ohne Scope; Vorhabens-Profil als Read-only-Vorschau. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
aa7e1626ea
commit
bbadf22d39
|
|
@ -1,3 +1,3 @@
|
||||||
APP_VERSION = "0.16.4-ap1.9b"
|
APP_VERSION = "0.16.5-ap1.12a"
|
||||||
DB_SCHEMA_VERSION = "014"
|
DB_SCHEMA_VERSION = "014"
|
||||||
APP_NAME = "jinkendo-kairo"
|
APP_NAME = "jinkendo-kairo"
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,8 @@ function AppRoutes() {
|
||||||
|
|
||||||
<Route path="/plan" element={<PlanLayout />}>
|
<Route path="/plan" element={<PlanLayout />}>
|
||||||
<Route index element={<PlanIndexRedirect />} />
|
<Route index element={<PlanIndexRedirect />} />
|
||||||
|
<Route path="portfolio" element={<MODE_ROUTE_COMPONENTS.planPortfolio />} />
|
||||||
|
<Route path="profile" element={<MODE_ROUTE_COMPONENTS.planProfile />} />
|
||||||
<Route path="structure" element={<MODE_ROUTE_COMPONENTS.planStructure />} />
|
<Route path="structure" element={<MODE_ROUTE_COMPONENTS.planStructure />} />
|
||||||
<Route path="gates" element={<MODE_ROUTE_COMPONENTS.planGates />} />
|
<Route path="gates" element={<MODE_ROUTE_COMPONENTS.planGates />} />
|
||||||
<Route path="inbox" element={<MODE_ROUTE_COMPONENTS.planInbox />} />
|
<Route path="inbox" element={<MODE_ROUTE_COMPONENTS.planInbox />} />
|
||||||
|
|
|
||||||
68
frontend/src/components/PlanOutlineNav.jsx
Normal file
68
frontend/src/components/PlanOutlineNav.jsx
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { NavLink, useLocation } from 'react-router-dom'
|
||||||
|
import { useProgramScope } from '../context/ProgramScopeContext.jsx'
|
||||||
|
import {
|
||||||
|
PLAN_OUTLINE_NODES,
|
||||||
|
resolvePlanOutlineActiveKey,
|
||||||
|
} from '../plan/planOutlineNodes.js'
|
||||||
|
|
||||||
|
export function PlanOutlineNav() {
|
||||||
|
const location = useLocation()
|
||||||
|
const { initiativeId, initiativeTitle, hrefWithScope } = useProgramScope()
|
||||||
|
const activeKey = resolvePlanOutlineActiveKey(location.pathname)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className="plan-outline-nav" aria-label="Plan-Outline">
|
||||||
|
<div className="plan-outline-nav__header">
|
||||||
|
<span className="plan-outline-nav__eyebrow">Planen</span>
|
||||||
|
{initiativeId ? (
|
||||||
|
<p className="plan-outline-nav__root" title={initiativeTitle || initiativeId}>
|
||||||
|
{initiativeTitle || 'Vorhaben'}
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<p className="plan-outline-nav__root plan-outline-nav__root--muted">
|
||||||
|
Portfolio — Vorhaben wählen
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul className="plan-outline-nav__list">
|
||||||
|
{PLAN_OUTLINE_NODES.map((node) => {
|
||||||
|
const needsInitiative = node.requiresInitiative && !initiativeId
|
||||||
|
const isDisabled = node.disabled || needsInitiative
|
||||||
|
|
||||||
|
if (isDisabled) {
|
||||||
|
return (
|
||||||
|
<li key={node.key}>
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
'plan-outline-nav__link plan-outline-nav__link--disabled' +
|
||||||
|
(activeKey === node.key ? ' plan-outline-nav__link--active' : '')
|
||||||
|
}
|
||||||
|
aria-disabled="true"
|
||||||
|
title={needsInitiative ? 'Vorhaben im Scope wählen' : node.hint}
|
||||||
|
>
|
||||||
|
{node.label}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={node.key}>
|
||||||
|
<NavLink
|
||||||
|
to={hrefWithScope(node.to)}
|
||||||
|
end
|
||||||
|
className={({ isActive }) =>
|
||||||
|
'plan-outline-nav__link' +
|
||||||
|
(isActive ? ' plan-outline-nav__link--active' : '')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{node.label}
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
||||||
13
frontend/src/pages/modes/PlanIndexRedirect.jsx
Normal file
13
frontend/src/pages/modes/PlanIndexRedirect.jsx
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { Navigate } from 'react-router-dom'
|
||||||
|
import { useProgramScope } from '../../context/ProgramScopeContext.jsx'
|
||||||
|
|
||||||
|
/** Portfolio ohne Scope → Übersicht; mit Vorhaben → Profil als Einstieg. */
|
||||||
|
export function PlanIndexRedirect() {
|
||||||
|
const { initiativeId, hrefWithScope } = useProgramScope()
|
||||||
|
|
||||||
|
if (initiativeId) {
|
||||||
|
return <Navigate to={hrefWithScope('/plan/profile')} replace />
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Navigate to="/plan/portfolio" replace />
|
||||||
|
}
|
||||||
|
|
@ -1,23 +1,19 @@
|
||||||
import { Navigate, Outlet } from 'react-router-dom'
|
import { Outlet } from 'react-router-dom'
|
||||||
import { ModeShell } from '../../components/ModeShell.jsx'
|
import { ModeShell } from '../../components/ModeShell.jsx'
|
||||||
import { ModeSubNav } from '../../components/ModeSubNav.jsx'
|
import { PlanOutlineNav } from '../../components/PlanOutlineNav.jsx'
|
||||||
|
|
||||||
const PLAN_NAV = [
|
|
||||||
{ to: '/plan/structure', label: 'Struktur', end: true },
|
|
||||||
{ to: '/plan/gates', label: 'Zielzustände', end: true },
|
|
||||||
{ to: '/plan/inbox', label: 'Eingang', end: true },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function PlanLayout() {
|
export function PlanLayout() {
|
||||||
return (
|
return (
|
||||||
<div className="app-page">
|
<div className="app-page plan-outline-shell">
|
||||||
<ModeShell modeKey="plan" />
|
<ModeShell modeKey="plan" />
|
||||||
<ModeSubNav items={PLAN_NAV} />
|
<div className="plan-outline-shell__body">
|
||||||
<Outlet />
|
<PlanOutlineNav />
|
||||||
|
<div className="plan-outline-shell__content">
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PlanIndexRedirect() {
|
export { PlanIndexRedirect } from './PlanIndexRedirect.jsx'
|
||||||
return <Navigate to="/plan/structure" replace />
|
|
||||||
}
|
|
||||||
|
|
|
||||||
64
frontend/src/pages/modes/PlanPortfolioPage.jsx
Normal file
64
frontend/src/pages/modes/PlanPortfolioPage.jsx
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { listInitiatives } from '../../api/initiatives.js'
|
||||||
|
import { useProgramScope } from '../../context/ProgramScopeContext.jsx'
|
||||||
|
import { LoadingState } from '../../components/LoadingState.jsx'
|
||||||
|
import { scopedPath } from '../../utils/routes.js'
|
||||||
|
|
||||||
|
export function PlanPortfolioPage() {
|
||||||
|
const { applyFromInitiative } = useProgramScope()
|
||||||
|
const [initiatives, setInitiatives] = useState(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false
|
||||||
|
listInitiatives()
|
||||||
|
.then((data) => {
|
||||||
|
if (!cancelled) setInitiatives(data)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (!cancelled) setInitiatives([])
|
||||||
|
})
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (initiatives === null) {
|
||||||
|
return <LoadingState message="Lade Vorhaben …" />
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="card plan-portfolio">
|
||||||
|
<h2 className="card-title">Programm planen</h2>
|
||||||
|
<p className="page-lead">
|
||||||
|
Wähle ein Vorhaben im Scope oben oder in der Liste — dann erscheint die Plan-Outline
|
||||||
|
mit Profil, Struktur, Zielzuständen und Eingang.
|
||||||
|
</p>
|
||||||
|
{initiatives.length === 0 ? (
|
||||||
|
<p className="muted">
|
||||||
|
Noch keine Vorhaben angelegt.{' '}
|
||||||
|
<Link to="/initiatives">Zum Vorhaben-Katalog</Link>
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<p className="muted plan-portfolio__hint">
|
||||||
|
Schnellstart — Vorhaben öffnen:
|
||||||
|
</p>
|
||||||
|
<ul className="scope-picker__list">
|
||||||
|
{initiatives.map((item) => (
|
||||||
|
<li key={item.id}>
|
||||||
|
<Link
|
||||||
|
to={scopedPath('/plan/profile', { initiativeId: item.id })}
|
||||||
|
className="scope-picker__link"
|
||||||
|
onClick={() => applyFromInitiative(item.id)}
|
||||||
|
>
|
||||||
|
{item.title}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
51
frontend/src/pages/modes/PlanProfilePage.jsx
Normal file
51
frontend/src/pages/modes/PlanProfilePage.jsx
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { RequireInitiativeScope } from '../../components/RequireInitiativeScope.jsx'
|
||||||
|
import { ScopedInitiativeProvider } from '../../layout/ScopedInitiativeProvider.jsx'
|
||||||
|
import { StatusBadge } from '../../components/StatusBadge.jsx'
|
||||||
|
import { LoadingState } from '../../components/LoadingState.jsx'
|
||||||
|
import { useInitiativeOperations } from '../../context/InitiativeOperationsContext.jsx'
|
||||||
|
|
||||||
|
function PlanProfileInner() {
|
||||||
|
const ops = useInitiativeOperations()
|
||||||
|
const { initiative, loading, error } = ops
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <LoadingState message="Lade Profil …" />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!initiative) {
|
||||||
|
return <p className="muted">Vorhaben nicht gefunden.</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="card plan-profile">
|
||||||
|
<header className="plan-profile__header">
|
||||||
|
<h2 className="card-title">{initiative.title}</h2>
|
||||||
|
<StatusBadge kind="initiative" status={initiative.status} />
|
||||||
|
</header>
|
||||||
|
{error && <p className="error">{error}</p>}
|
||||||
|
{initiative.goal ? (
|
||||||
|
<div className="plan-profile__field">
|
||||||
|
<h3 className="plan-profile__label">Ziel</h3>
|
||||||
|
<p>{initiative.goal}</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="muted">
|
||||||
|
Noch kein Ziel hinterlegt — vollständiges Profil folgt mit Archetypen (AP1.10).
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<p className="muted plan-profile__note">
|
||||||
|
Bearbeitung per Modal kommt in AP1.10 (Entity Field System).
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PlanProfilePage() {
|
||||||
|
return (
|
||||||
|
<RequireInitiativeScope lead="Das Vorhabens-Profil gehört zu einem gewählten Scope.">
|
||||||
|
<ScopedInitiativeProvider>
|
||||||
|
<PlanProfileInner />
|
||||||
|
</ScopedInitiativeProvider>
|
||||||
|
</RequireInitiativeScope>
|
||||||
|
)
|
||||||
|
}
|
||||||
34
frontend/src/plan/planOutlineNodes.js
Normal file
34
frontend/src/plan/planOutlineNodes.js
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
/**
|
||||||
|
* Top-level Plan-Outline-Knoten (AP1.12a).
|
||||||
|
* Unterknoten (Projekt-Baum, Gate-Graph) folgen in AP1.12b+.
|
||||||
|
*
|
||||||
|
* @typedef {{ key: string, to: string, label: string, requiresInitiative?: boolean, disabled?: boolean, hint?: string }} PlanOutlineNode
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @type {PlanOutlineNode[]} */
|
||||||
|
export const PLAN_OUTLINE_NODES = [
|
||||||
|
{ key: 'profile', to: '/plan/profile', label: 'Profil', requiresInitiative: true },
|
||||||
|
{ key: 'structure', to: '/plan/structure', label: 'Struktur', requiresInitiative: true },
|
||||||
|
{ key: 'gates', to: '/plan/gates', label: 'Zielzustände', requiresInitiative: true },
|
||||||
|
{ key: 'inbox', to: '/plan/inbox', label: 'Eingang', requiresInitiative: true },
|
||||||
|
{
|
||||||
|
key: 'work',
|
||||||
|
to: '/plan/work',
|
||||||
|
label: 'Arbeit',
|
||||||
|
requiresInitiative: true,
|
||||||
|
disabled: true,
|
||||||
|
hint: 'AP1.12 — Actions im Plan-Kontext',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} pathname
|
||||||
|
* @returns {string | null}
|
||||||
|
*/
|
||||||
|
export function resolvePlanOutlineActiveKey(pathname) {
|
||||||
|
const path = pathname.replace(/\/$/, '') || pathname
|
||||||
|
const match = PLAN_OUTLINE_NODES.find(
|
||||||
|
(node) => path === node.to || path.startsWith(`${node.to}/`),
|
||||||
|
)
|
||||||
|
return match?.key ?? null
|
||||||
|
}
|
||||||
22
frontend/src/plan/planOutlineNodes.test.js
Normal file
22
frontend/src/plan/planOutlineNodes.test.js
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { PLAN_OUTLINE_NODES, resolvePlanOutlineActiveKey } from './planOutlineNodes.js'
|
||||||
|
|
||||||
|
describe('planOutlineNodes', () => {
|
||||||
|
it('lists plan sections in product order', () => {
|
||||||
|
expect(PLAN_OUTLINE_NODES.map((n) => n.key)).toEqual([
|
||||||
|
'profile',
|
||||||
|
'structure',
|
||||||
|
'gates',
|
||||||
|
'inbox',
|
||||||
|
'work',
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('resolves active outline key from pathname', () => {
|
||||||
|
expect(resolvePlanOutlineActiveKey('/plan/profile')).toBe('profile')
|
||||||
|
expect(resolvePlanOutlineActiveKey('/plan/structure')).toBe('structure')
|
||||||
|
expect(resolvePlanOutlineActiveKey('/plan/gates')).toBe('gates')
|
||||||
|
expect(resolvePlanOutlineActiveKey('/plan/inbox')).toBe('inbox')
|
||||||
|
expect(resolvePlanOutlineActiveKey('/plan/portfolio')).toBe(null)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -6,6 +6,8 @@ import { PlanLayout, PlanIndexRedirect } from '../pages/modes/PlanLayout.jsx'
|
||||||
import { PlanStructurePage } from '../pages/modes/PlanStructurePage.jsx'
|
import { PlanStructurePage } from '../pages/modes/PlanStructurePage.jsx'
|
||||||
import { PlanGatesPage } from '../pages/modes/PlanGatesPage.jsx'
|
import { PlanGatesPage } from '../pages/modes/PlanGatesPage.jsx'
|
||||||
import { PlanInboxPage } from '../pages/modes/PlanInboxPage.jsx'
|
import { PlanInboxPage } from '../pages/modes/PlanInboxPage.jsx'
|
||||||
|
import { PlanProfilePage } from '../pages/modes/PlanProfilePage.jsx'
|
||||||
|
import { PlanPortfolioPage } from '../pages/modes/PlanPortfolioPage.jsx'
|
||||||
import { ControlLayout, ControlIndexRedirect } from '../pages/modes/ControlLayout.jsx'
|
import { ControlLayout, ControlIndexRedirect } from '../pages/modes/ControlLayout.jsx'
|
||||||
import { ControlStatusPage } from '../pages/modes/ControlStatusPage.jsx'
|
import { ControlStatusPage } from '../pages/modes/ControlStatusPage.jsx'
|
||||||
import { ControlJourneyPage } from '../pages/modes/ControlJourneyPage.jsx'
|
import { ControlJourneyPage } from '../pages/modes/ControlJourneyPage.jsx'
|
||||||
|
|
@ -125,6 +127,8 @@ export function getViewByKey(key) {
|
||||||
export const MODE_ROUTE_COMPONENTS = {
|
export const MODE_ROUTE_COMPONENTS = {
|
||||||
workToday: WorkTodayPage,
|
workToday: WorkTodayPage,
|
||||||
workMine: WorkMinePage,
|
workMine: WorkMinePage,
|
||||||
|
planPortfolio: PlanPortfolioPage,
|
||||||
|
planProfile: PlanProfilePage,
|
||||||
planStructure: PlanStructurePage,
|
planStructure: PlanStructurePage,
|
||||||
planGates: PlanGatesPage,
|
planGates: PlanGatesPage,
|
||||||
planInbox: PlanInboxPage,
|
planInbox: PlanInboxPage,
|
||||||
|
|
|
||||||
|
|
@ -291,3 +291,122 @@
|
||||||
.section-header--compact {
|
.section-header--compact {
|
||||||
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 {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-profile__label {
|
||||||
|
margin: 0 0 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: var(--jk-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-profile__field {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-profile__note {
|
||||||
|
margin-top: 16px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-portfolio__hint {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
const MODE_DEFAULTS = {
|
const MODE_DEFAULTS = {
|
||||||
cockpit: { portfolioHub: '/cockpit', initiativeHub: '/cockpit' },
|
cockpit: { portfolioHub: '/cockpit', initiativeHub: '/cockpit' },
|
||||||
work: { portfolioHub: '/work/today', initiativeHub: '/work/today' },
|
work: { portfolioHub: '/work/today', initiativeHub: '/work/today' },
|
||||||
plan: { portfolioHub: '/plan/structure', initiativeHub: '/plan/structure' },
|
plan: { portfolioHub: '/plan/portfolio', initiativeHub: '/plan/profile' },
|
||||||
control: { portfolioHub: '/control/status', initiativeHub: '/control/status' },
|
control: { portfolioHub: '/control/status', initiativeHub: '/control/status' },
|
||||||
team: { portfolioHub: '/team', initiativeHub: '/team' },
|
team: { portfolioHub: '/team', initiativeHub: '/team' },
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user