feat(nav): add launch buttons for My Timetable and My Classes sections

Add useNavigate + Launch icon to TreeItem; timetable section shows a
launch button routing to /my-lessons, classes section routes to /my-classes.
Both buttons only appear when the section status is populated.
Exclude classes section from the unlinked/pending icon fallback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
kcar 2026-05-27 11:22:26 +01:00
parent 61ef95a35e
commit 9438e17f88

View File

@ -19,7 +19,9 @@ import {
GridOn as GridIcon, GridOn as GridIcon,
Settings as SetupIcon, Settings as SetupIcon,
Edit as EditIcon, Edit as EditIcon,
Launch as LaunchIcon,
} from '@mui/icons-material'; } from '@mui/icons-material';
import { useNavigate } from 'react-router-dom';
import { useNavigationStore } from '../../../../../../stores/navigationStore'; import { useNavigationStore } from '../../../../../../stores/navigationStore';
import { useAuth } from '../../../../../../contexts/AuthContext'; import { useAuth } from '../../../../../../contexts/AuthContext';
import { NeoGraphNode } from '../../../../../../types/navigation'; import { NeoGraphNode } from '../../../../../../types/navigation';
@ -124,6 +126,7 @@ interface TreeItemProps {
function TreeItem({ node, depth, onSelect, onExpand }: TreeItemProps) { function TreeItem({ node, depth, onSelect, onExpand }: TreeItemProps) {
const ctx = useContext(NavPanelContext); const ctx = useContext(NavPanelContext);
const navigate = useNavigate();
const [expanded, setExpanded] = useState(node.is_section && node.status === 'populated'); const [expanded, setExpanded] = useState(node.is_section && node.status === 'populated');
const [children, setChildren] = useState<TreeNode[]>(node.children || []); const [children, setChildren] = useState<TreeNode[]>(node.children || []);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -132,6 +135,7 @@ function TreeItem({ node, depth, onSelect, onExpand }: TreeItemProps) {
const isCalendarSection = isSection && node.section_id === 'calendar'; const isCalendarSection = isSection && node.section_id === 'calendar';
const isTimetableSection = isSection && node.section_id === 'timetable'; const isTimetableSection = isSection && node.section_id === 'timetable';
const isSchoolSection = isSection && node.section_id === 'school'; const isSchoolSection = isSection && node.section_id === 'school';
const isClassesSection = isSection && node.section_id === 'classes';
const SectionIcon = node.section_id ? SECTION_ICONS[node.section_id] : null; const SectionIcon = node.section_id ? SECTION_ICONS[node.section_id] : null;
const Icon = SectionIcon || NODE_ICONS[node.node_type] || HomeIcon; const Icon = SectionIcon || NODE_ICONS[node.node_type] || HomeIcon;
@ -196,6 +200,8 @@ function TreeItem({ node, depth, onSelect, onExpand }: TreeItemProps) {
const showLegacySetup = isTimetableSection && node.status === 'empty' && !ss; const showLegacySetup = isTimetableSection && node.status === 'empty' && !ss;
const showTimetableEdit = isTimetableSection && node.status === 'populated' const showTimetableEdit = isTimetableSection && node.status === 'populated'
&& ss && ss.status !== 'no_school' && !!ss.teacher_has_timetable; && ss && ss.status !== 'no_school' && !!ss.teacher_has_timetable;
const showTimetableView = isTimetableSection && node.status === 'populated';
const showClassesView = isClassesSection && node.status === 'populated';
if (isSection) { if (isSection) {
return ( return (
@ -223,7 +229,7 @@ function TreeItem({ node, depth, onSelect, onExpand }: TreeItemProps) {
</IconButton> </IconButton>
) )
)} )}
{isEmpty && !isCalendarSection && !isTimetableSection && !isSchoolSection && ( {isEmpty && !isCalendarSection && !isTimetableSection && !isSchoolSection && !isClassesSection && (
<Box sx={{ width: 18, display: 'flex', alignItems: 'center', justifyContent: 'center' }}> <Box sx={{ width: 18, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
{node.status === 'no_school' {node.status === 'no_school'
? <UnlinkedIcon sx={{ fontSize: 11, color: 'text.disabled' }} /> ? <UnlinkedIcon sx={{ fontSize: 11, color: 'text.disabled' }} />
@ -249,7 +255,7 @@ function TreeItem({ node, depth, onSelect, onExpand }: TreeItemProps) {
{node.label} {node.label}
</Typography> </Typography>
{isEmpty && !isCalendarSection && !isTimetableSection && !isSchoolSection && node.status && ( {isEmpty && !isCalendarSection && !isTimetableSection && !isSchoolSection && !isClassesSection && node.status && (
<Tooltip title={STATUS_MESSAGES[node.status]} placement="right"> <Tooltip title={STATUS_MESSAGES[node.status]} placement="right">
<Typography variant="caption" sx={{ fontSize: '0.6rem', color: 'text.disabled', ml: 0.5, flexShrink: 0 }}> <Typography variant="caption" sx={{ fontSize: '0.6rem', color: 'text.disabled', ml: 0.5, flexShrink: 0 }}>
{node.status === 'no_school' ? '—' : '…'} {node.status === 'no_school' ? '—' : '…'}
@ -319,6 +325,28 @@ function TreeItem({ node, depth, onSelect, onExpand }: TreeItemProps) {
</IconButton> </IconButton>
</Tooltip> </Tooltip>
)} )}
{showTimetableView && (
<Tooltip title="View as lesson list" placement="right">
<IconButton
size="small"
sx={{ p: 0.25, ml: 0.5, color: 'text.secondary' }}
onClick={e => { e.stopPropagation(); navigate('/my-lessons'); }}
>
<LaunchIcon sx={{ fontSize: 13 }} />
</IconButton>
</Tooltip>
)}
{showClassesView && (
<Tooltip title="View my classes" placement="right">
<IconButton
size="small"
sx={{ p: 0.25, ml: 0.5, color: 'text.secondary' }}
onClick={e => { e.stopPropagation(); navigate('/my-classes'); }}
>
<LaunchIcon sx={{ fontSize: 13 }} />
</IconButton>
</Tooltip>
)}
</Box> </Box>
{/* Calendar mode toggle */} {/* Calendar mode toggle */}