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.
51 lines
1.6 KiB
TypeScript
51 lines
1.6 KiB
TypeScript
import { ACCENTS, useAppearance } from '../lib/appearance'
|
|
|
|
export function ThemeControls() {
|
|
const { mode, accent, setAccent, toggle } = useAppearance()
|
|
|
|
return (
|
|
<div className="appearance">
|
|
<div className="swatches" role="radiogroup" aria-label="Accent colour">
|
|
{ACCENTS.map((a) => (
|
|
<button
|
|
key={a.id}
|
|
type="button"
|
|
role="radio"
|
|
aria-checked={accent === a.id}
|
|
aria-label={a.label}
|
|
title={a.label}
|
|
className={`swatch${accent === a.id ? ' on' : ''}`}
|
|
data-accent={a.id}
|
|
onClick={() => setAccent(a.id)}
|
|
/>
|
|
))}
|
|
</div>
|
|
<button
|
|
type="button"
|
|
className="mode-toggle"
|
|
onClick={toggle}
|
|
title={mode === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'}
|
|
aria-label={mode === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'}
|
|
>
|
|
{mode === 'dark' ? <SunIcon /> : <MoonIcon />}
|
|
</button>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function SunIcon() {
|
|
return (
|
|
<svg viewBox="0 0 20 20" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round">
|
|
<circle cx="10" cy="10" r="3.6" />
|
|
<path d="M10 1.6v2.1M10 16.3v2.1M18.4 10h-2.1M3.7 10H1.6M15.9 4.1l-1.5 1.5M5.6 14.4l-1.5 1.5M15.9 15.9l-1.5-1.5M5.6 5.6L4.1 4.1" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function MoonIcon() {
|
|
return (
|
|
<svg viewBox="0 0 20 20" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M17.5 12.4A7.9 7.9 0 0 1 7.6 2.5a7.9 7.9 0 1 0 9.9 9.9z" />
|
|
</svg>
|
|
)
|
|
}
|