App for the Mega Infrastructure AI Integration programme (A000) and Mega Plant (B110). 100 activities across 2 programmes, 06 Oct 2026 - 31 Aug 2027, parsed from the client PDF and frozen as an immutable baseline. Views: Dashboard, Timeline (month grid + week grid matching the PDF's Friday-anchored week numbering), Todo list, WBS, Data. Notes on the source data: - Hierarchy is derived from the PDF's indentation, not the activity code. Codes do not sort structurally (A490 sits under Workforce, A510 under Financials), so a code-ordered import would produce a wrong tree. - The schedule is built from the dates, not the PDF's "Durn" column, which disagrees with its own dates for 13 of the 100 activities. - Spec issues found in the PDF are surfaced in-app under Data: 2 unresolved "?" placeholders, 6 activities sharing one stretched 6-week window while declared 15 days, 13 duration mismatches, 17 spelling corrections (original wording preserved and viewable). Progress (status, % complete, note) is stored behind a pluggable adapter so the site can be static today and point at a shared JSON store later without touching the UI.
77 lines
2.3 KiB
TypeScript
77 lines
2.3 KiB
TypeScript
import { useState } from 'react'
|
|
import { StoreProvider, useStore } from './lib/store'
|
|
import { Dashboard } from './components/Dashboard'
|
|
import { Gantt } from './components/Gantt'
|
|
import { Todos } from './components/Todos'
|
|
import { Wbs } from './components/Wbs'
|
|
import { DataView } from './components/DataView'
|
|
import { ActivityPanel } from './components/ActivityPanel'
|
|
import { ThemeControls } from './components/ThemeControls'
|
|
import './styles.css'
|
|
|
|
type Tab = 'dashboard' | 'timeline' | 'todos' | 'wbs' | 'data'
|
|
|
|
const TABS: [Tab, string][] = [
|
|
['dashboard', 'Dashboard'],
|
|
['timeline', 'Timeline'],
|
|
['todos', 'Todo list'],
|
|
['wbs', 'WBS'],
|
|
['data', 'Data'],
|
|
]
|
|
|
|
function Shell() {
|
|
const [tab, setTab] = useState<Tab>('dashboard')
|
|
const [open, setOpen] = useState<string | null>(null)
|
|
const { views, index } = useStore()
|
|
|
|
const leaves = [...views.values()].filter((v) => v.isLeaf)
|
|
const done = leaves.filter((v) => v.status === 'done').length
|
|
const overall = Math.round(
|
|
leaves.reduce((s, v) => s + v.percent, 0) / (leaves.length || 1),
|
|
)
|
|
|
|
return (
|
|
<div className="app">
|
|
<header className="topbar">
|
|
<div className="brand">
|
|
<strong>Mega Infrastructure</strong>
|
|
<span>AI Integration — project tracker</span>
|
|
</div>
|
|
<nav>
|
|
{TABS.map(([k, l]) => (
|
|
<button key={k} className={tab === k ? 'on' : ''} onClick={() => setTab(k)}>
|
|
{l}
|
|
{k === 'todos' && done > 0 && <em className="badge">{done}</em>}
|
|
</button>
|
|
))}
|
|
</nav>
|
|
<div className="topmeta">
|
|
<div className="bar sm">
|
|
<i style={{ width: `${overall}%` }} />
|
|
</div>
|
|
<span>{overall}%</span>
|
|
<span className="muted sm">{index.order.length} activities</span>
|
|
<ThemeControls />
|
|
</div>
|
|
</header>
|
|
|
|
<main>
|
|
{tab === 'dashboard' && <Dashboard onOpen={setOpen} />}
|
|
{tab === 'timeline' && <Gantt onOpen={setOpen} />}
|
|
{tab === 'todos' && <Todos onOpen={setOpen} />}
|
|
{tab === 'wbs' && <Wbs onOpen={setOpen} />}
|
|
{tab === 'data' && <DataView onOpen={setOpen} />}
|
|
</main>
|
|
|
|
<ActivityPanel code={open} onClose={() => setOpen(null)} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default function App() {
|
|
return (
|
|
<StoreProvider>
|
|
<Shell />
|
|
</StoreProvider>
|
|
)
|
|
}
|