fix(nav): register Journal/Planner shapes, fix headerColor crash, enable academic week expansion

Bugs fixed:
- 'cc-teacher-timetable-node' missing from NODE_TYPE_THEMES caused
  Cannot read properties of undefined (reading 'headerColor') crash
  when clicking My Timetable
- 'cc-journal-node' and 'cc-planner-node' had no shape utils registered,
  causing 'No shape util found for type' error on Journal/Planner click
- Added null safety (?? fallback) to getNodeTheme to prevent future crashes
  from any other unmapped type
- Removed AcademicWeek from canExpand exclusion so weeks can be expanded
  to show individual academic days

Added:
- CCJournalNodeShapeUtil and CCPlannerNodeShapeUtil (stub shapes)
- CCJournalNodeProps and CCPlannerNodeProps types
- Journal/Planner added to CCNodeTypes, ccGraphShapeProps, NODE_TYPE_THEMES
- 'cc-department-structure-node' mapping added to NODE_TYPE_THEMES

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
kcar 2026-05-27 10:55:40 +01:00
parent 0f5dbd12bf
commit 61ef95a35e
7 changed files with 117 additions and 2 deletions

View File

@ -0,0 +1,36 @@
import { CCBaseShapeUtil } from '../CCBaseShapeUtil'
import { CCBaseShape } from '../cc-types'
import { ccGraphShapeProps, getDefaultCCJournalNodeProps } from './cc-graph-props'
import { getNodeStyles, NODE_THEMES, NODE_TYPE_THEMES } from './cc-graph-styles'
import { CCJournalNodeProps } from './cc-graph-types'
export interface CCJournalNodeShape extends CCBaseShape {
type: 'cc-journal-node'
props: CCJournalNodeProps
}
export class CCJournalNodeShapeUtil extends CCBaseShapeUtil<CCJournalNodeShape> {
static type = 'cc-journal-node' as const
static props = ccGraphShapeProps['cc-journal-node']
getDefaultProps(): CCJournalNodeShape['props'] {
const defaultProps = getDefaultCCJournalNodeProps()
const theme = NODE_THEMES[NODE_TYPE_THEMES[CCJournalNodeShapeUtil.type]] ?? NODE_THEMES.resource
return {
...defaultProps,
headerColor: theme.headerColor,
}
}
DefaultComponent = () => null
renderContent = (shape: CCJournalNodeShape) => {
const styles = getNodeStyles(shape.type)
return (
<div style={styles.container}>
<div style={{ ...styles.header, color: shape.props.headerColor }}>Journal</div>
<div style={{ fontSize: 11, color: 'var(--color-text-2)' }}>Personal teaching journal</div>
</div>
)
}
}

View File

@ -0,0 +1,36 @@
import { CCBaseShapeUtil } from '../CCBaseShapeUtil'
import { CCBaseShape } from '../cc-types'
import { ccGraphShapeProps, getDefaultCCPlannerNodeProps } from './cc-graph-props'
import { getNodeStyles, NODE_THEMES, NODE_TYPE_THEMES } from './cc-graph-styles'
import { CCPlannerNodeProps } from './cc-graph-types'
export interface CCPlannerNodeShape extends CCBaseShape {
type: 'cc-planner-node'
props: CCPlannerNodeProps
}
export class CCPlannerNodeShapeUtil extends CCBaseShapeUtil<CCPlannerNodeShape> {
static type = 'cc-planner-node' as const
static props = ccGraphShapeProps['cc-planner-node']
getDefaultProps(): CCPlannerNodeShape['props'] {
const defaultProps = getDefaultCCPlannerNodeProps()
const theme = NODE_THEMES[NODE_TYPE_THEMES[CCPlannerNodeShapeUtil.type]] ?? NODE_THEMES.resource
return {
...defaultProps,
headerColor: theme.headerColor,
}
}
DefaultComponent = () => null
renderContent = (shape: CCPlannerNodeShape) => {
const styles = getNodeStyles(shape.type)
return (
<div style={styles.container}>
<div style={{ ...styles.header, color: shape.props.headerColor }}>Planner</div>
<div style={{ fontSize: 11, color: 'var(--color-text-2)' }}>Lesson and weekly planner</div>
</div>
)
}
}

View File

@ -272,6 +272,14 @@ export const ccGraphShapeProps = {
school_db_name: T.string, school_db_name: T.string,
school_period_id: T.string, school_period_id: T.string,
}, },
'cc-journal-node': {
...graphBaseProps,
path: T.string,
},
'cc-planner-node': {
...graphBaseProps,
path: T.string,
},
} as const } as const
// Default props getters // Default props getters
@ -640,3 +648,17 @@ export const getDefaultCCUserTeacherTimetableNodeProps = () => ({
school_db_name: '', school_db_name: '',
school_timetable_id: '', school_timetable_id: '',
}) })
export const getDefaultCCJournalNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Journal',
__primarylabel__: 'Journal',
path: '',
})
export const getDefaultCCPlannerNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Planner',
__primarylabel__: 'Planner',
path: '',
})

View File

@ -148,12 +148,18 @@ export const NODE_TYPE_THEMES: Record<string, keyof typeof NODE_THEMES> = {
'cc-department-node': 'resource', 'cc-department-node': 'resource',
'cc-room-node': 'resource', 'cc-room-node': 'resource',
'cc-subject-class-node': 'resource', 'cc-subject-class-node': 'resource',
// Timetable nodes (missing from original map)
'cc-teacher-timetable-node': 'academic',
'cc-department-structure-node': 'resource',
// Personal nodes
'cc-journal-node': 'calendar',
'cc-planner-node': 'calendar',
} as const } as const
// Helper function to get theme for a node type // Helper function to get theme for a node type
export const getNodeTheme = (nodeType: string) => { export const getNodeTheme = (nodeType: string) => {
const themeKey = NODE_TYPE_THEMES[nodeType] const themeKey = NODE_TYPE_THEMES[nodeType]
return themeKey ? NODE_THEMES[themeKey] : NODE_THEMES.resource // Default to resource theme return (themeKey ? NODE_THEMES[themeKey] : null) ?? NODE_THEMES.resource
} }
// Helper function to get theme from primary label // Helper function to get theme from primary label

View File

@ -279,6 +279,14 @@ export type CCTimetableLessonNodeProps = CCGraphShapeProps & {
} }
// Define a type-safe mapping of node types to their configurations // Define a type-safe mapping of node types to their configurations
export type CCJournalNodeProps = CCGraphShapeProps & {
path: string
}
export type CCPlannerNodeProps = CCGraphShapeProps & {
path: string
}
export type CCNodeTypes = { export type CCNodeTypes = {
User: { props: CCUserNodeProps } User: { props: CCUserNodeProps }
Developer: { props: CCUserNodeProps } Developer: { props: CCUserNodeProps }
@ -316,6 +324,8 @@ export type CCNodeTypes = {
DepartmentStructure: { props: CCDepartmentStructureNodeProps } DepartmentStructure: { props: CCDepartmentStructureNodeProps }
UserTeacherTimetable: { props: CCUserTeacherTimetableNodeProps } UserTeacherTimetable: { props: CCUserTeacherTimetableNodeProps }
UserTimetableLesson: { props: CCTimetableLessonNodeProps } UserTimetableLesson: { props: CCTimetableLessonNodeProps }
Journal: { props: CCJournalNodeProps }
Planner: { props: CCPlannerNodeProps }
} }
// Helper function to get shape type from node type // Helper function to get shape type from node type
@ -376,5 +386,7 @@ export const isValidNodeType = (type: string): type is keyof CCNodeTypes => {
DepartmentStructure: true, DepartmentStructure: true,
UserTeacherTimetable: true, UserTeacherTimetable: true,
UserTimetableLesson: true, UserTimetableLesson: true,
Journal: true,
Planner: true,
}; };
} }

View File

@ -43,6 +43,8 @@ import { CCDepartmentStructureNodeShapeUtil } from './cc-base/cc-graph/CCDepartm
import { CCUserTeacherTimetableNodeShapeUtil } from './cc-base/cc-graph/CCUserTeacherTimetableNodeShapeUtil' import { CCUserTeacherTimetableNodeShapeUtil } from './cc-base/cc-graph/CCUserTeacherTimetableNodeShapeUtil'
import { CCSearchShapeUtil } from './cc-base/cc-search/CCSearchShapeUtil' import { CCSearchShapeUtil } from './cc-base/cc-search/CCSearchShapeUtil'
import { CCWebBrowserShapeUtil } from './cc-base/cc-web-browser/CCWebBrowserUtil' import { CCWebBrowserShapeUtil } from './cc-base/cc-web-browser/CCWebBrowserUtil'
import { CCJournalNodeShapeUtil } from './cc-base/cc-graph/CCJournalNodeShapeUtil'
import { CCPlannerNodeShapeUtil } from './cc-base/cc-graph/CCPlannerNodeShapeUtil'
// Define all shape utils in a single object for easy maintenance // Define all shape utils in a single object for easy maintenance
export const ShapeUtils = { export const ShapeUtils = {
CCSlideShow: CCSlideShowShapeUtil, CCSlideShow: CCSlideShowShapeUtil,
@ -89,6 +91,8 @@ export const ShapeUtils = {
CCUserTeacherTimetableNode: CCUserTeacherTimetableNodeShapeUtil, CCUserTeacherTimetableNode: CCUserTeacherTimetableNodeShapeUtil,
CCSearch: CCSearchShapeUtil, CCSearch: CCSearchShapeUtil,
CCWebBrowser: CCWebBrowserShapeUtil, CCWebBrowser: CCWebBrowserShapeUtil,
CCJournalNode: CCJournalNodeShapeUtil,
CCPlannerNode: CCPlannerNodeShapeUtil,
} }
export const allShapeUtils = Object.values(ShapeUtils) export const allShapeUtils = Object.values(ShapeUtils)

View File

@ -137,7 +137,6 @@ function TreeItem({ node, depth, onSelect, onExpand }: TreeItemProps) {
const canExpand = node.has_children !== false const canExpand = node.has_children !== false
&& node.node_type !== 'CalendarDay' && node.node_type !== 'CalendarDay'
&& node.node_type !== 'AcademicWeek'
&& node.status !== 'empty' && node.status !== 'empty'
&& node.status !== 'no_school' && node.status !== 'no_school'
&& node.status !== 'not_initialized'; && node.status !== 'not_initialized';