@if (!ShowForm) {

Programme Catalogue

{{ programmes().length }} active programmes
Sort:
{{ totalCount() }} programmes
@if (displayMode() === 'grid') {
@for (prog of filteredProgrammes(); track prog.ProgrammeId) {
{{ getCardInitial(prog) }}
{{ prog.ProgrammeTitle }}
{{ prog.ProgrammeCode }}
@if (prog.DomainName) { {{ prog.DomainName }} } {{ getProgrammeTypeLabel(prog.ProgrammeType) }} {{ getDeliveryLabel(prog.DeliveryMode) }} @if (prog.ComplexityLevel !== null) { {{ getComplexityLabel(prog.ComplexityLevel) }} } {{ prog.IsActive === 1 ? 'Active' : 'Inactive' }}
{{ prog.DurationHours ? (prog.DurationHours + ' hrs') : '—' }} Duration
{{ prog.MaxCapacity || '—' }} Max Seats
{{ prog.SessionCount || '—' }} Sessions
{{ prog.PassRate ? (prog.PassRate + '%') : '—' }} Pass Rate
} @if (filteredProgrammes().length === 0) {
No programmes match the selected filters.
}
} @if (displayMode() === 'list') {
@for (prog of filteredProgrammes(); track prog.ProgrammeId) { } @if (filteredProgrammes().length === 0) { }
Code Programme Title Domain Type Delivery Complexity Hrs Seats Refresher Status Actions
{{ prog.ProgrammeCode }} {{ prog.ProgrammeTitle }} @if (prog.ProgrammeVersionNo) { {{ prog.ProgrammeVersionNo }} } {{ prog.DomainName || '—' }} {{ getProgrammeTypeLabel(prog.ProgrammeType) }} {{ getDeliveryLabel(prog.DeliveryMode) }} {{ getComplexityLabel(prog.ComplexityLevel) }} {{ prog.DurationHours || '—' }} @if (prog.MinCapacity || prog.MaxCapacity) { {{ prog.MinCapacity }}–{{ prog.MaxCapacity }} } @else { — } {{ +prog.IsRefresher === 0 ? 'No' : 'Yes' }} {{ prog.IsActive === 1 ? 'Active' : 'Inactive' }}
No programmes found.
}
} @if (ShowForm) {
@if (form.bFormReady) {
@for (tab of TABS; track $index; let i = $index) { }
layers
{{ sessions().length }} Sessions
schedule
{{ totalSessionHours() }}h Total hours
label
{{ totalTopicCount() }} Topics
@for (s of sessions(); track $index; let si = $index) {
drag_indicator
{{ (si + 1).toString().padStart(2, '0') }}
@if (s.SessionCode) { {{ s.SessionCode }} } {{ SESSION_TYPE_LABELS[s.SessionType] ?? 'Session' }}
{{ s.SessionTitle }}
schedule {{ s.DurationHours }}h chat {{ SESSION_DELIV_LABELS[s.SessionDelivMode] ?? 'Classroom' }} @if ((s.Topics ?? []).length > 0) { label {{ (s.Topics ?? []).length }} topics }
@if (expandedSessionIndex() === si) {
@if (s.DeliveryNote) {
DESCRIPTION

{{ s.DeliveryNote }}

}
TOPICS @if ((s.Topics ?? []).length > 0) { {{ (s.Topics ?? []).length }} total }
@for (t of (s.Topics ?? []); track $index; let ti = $index) {
{{ ti + 1 }} {{ t.TopicTitle }} schedule {{ t.DurationHours ?? 0 }} h
}
}
} @if (sessions().length === 0) {
layers

No sessions yet. Click + Add Session to begin.

}
account_tree
{{ skillMaps().length }} Skill Maps
@for (sm of skillMaps(); track $index; let smi = $index) {
{{ (smi + 1).toString().padStart(2, '0') }}
{{ sm.SkillName || '—' }}
@if (sm.QualifiesForLevelNo !== null && sm.QualifiesForLevelNo !== undefined) { {{ SKILL_LEVEL_LABELS[sm.QualifiesForLevelNo] }} } @if (sm.SessionTitle) { {{ sm.SessionTitle }} } @if (sm.ModelName) { {{ sm.ModelName }} } @if (sm.ProductFamilyName) { {{ sm.ProductFamilyName }} }
@if (sm.IsAutoPropose) { Auto Propose } @if (sm.RequiresPassedAssess) { Requires Assessment } @if (sm.IsDirectEvidence) { Direct Evidence } @if (sm.Remarks) { {{ sm.Remarks }} }
} @if (skillMaps().length === 0) {
account_tree

No skill maps yet. Click + Add Skill Map to begin.

}
checklist
{{ prerequisites().length }} Prerequisites
@for (pr of prerequisites(); track $index; let pri = $index) {
{{ (pri + 1).toString().padStart(2, '0') }}
{{ pr.PrerequisiteProgrammeName || pr.ProgrammePrereqName || '—' }}
{{ pr.IsHard === 1 ? 'Mandatory' : 'Optional' }} @if (pr.ValidWithinDays > 0) { Valid within {{ pr.ValidWithinDays }} days } @if (pr.Remarks) { {{ pr.Remarks }} }
} @if (prerequisites().length === 0) {
checklist

No prerequisites yet. Click + Add Prerequisite to begin.

}
history
{{ revisionHistory().length }} Revisions
@if (revisionHistoryLoading()) {

Loading revision history…

} @if (!revisionHistoryLoading() && revisionHistory().length === 0) {
history

No revisions recorded yet — every destructive update to this programme is snapshotted here automatically.

} @if (!revisionHistoryLoading() && revisionHistory().length > 0) { @for (rev of revisionHistory(); track rev.ProgrammeRevisionId) { }
Rev # Effective From Change Summary Revised By Revised On
{{ rev.RevisionNo }} {{ rev.EffectiveFrom | date:'dd MMM yyyy' }} {{ rev.ChangeSummary || '—' }} {{ rev.RevisedByName || '—' }} {{ rev.RevisedOn | date:'dd MMM yyyy, h:mm a' }}
}
} @if (subFormType() !== null) {
}
} @if (showBulkImport()) {
Bulk Import Training Programmes
clear

Session Code/Title are required — every programme needs at least one session to save, so this import creates one basic session per row. Domain is matched by name against the Training Domain master automatically — no need to look up a numeric id.

@if (bulkLookupsLoading()) {
Loading Training Domain master list…
} @if (bulkLookupsError()) {
Couldn't load the Training Domain master list needed to match names. Retry
}
@if (bulkFileName()) { {{ bulkFileName() }} }
@if (bulkRows().length === 0) {

Expected columns:

    @for (col of bulkColumns; track col.key) {
  • {{ col.label }} @if (col.required) { * } @if (col.hint) { — {{ col.hint }} }
  • }
} @if (bulkRows().length > 0) {
{{ bulkRows().length }} rows @if (bulkInvalidCount() > 0) { {{ bulkInvalidCount() }} invalid } @if (bulkSuccessCount() > 0) { {{ bulkSuccessCount() }} imported } @if (bulkErrorCount() > 0) { {{ bulkErrorCount() }} failed } @if (bulkImporting()) { Importing… {{ bulkProcessedCount() }} / {{ bulkValidCount() }} }
@for (col of bulkColumns; track col.key) { } @for (row of bulkRows(); track row._rowNum) { @for (col of bulkColumns; track col.key) { } }
#{{ col.label }}Status
{{ row._rowNum }}{{ row[col.key] }} {{ row._status }} @if (row._errors.length) {
{{ row._errors.join(', ') }}
} @if (row._message) {
{{ row._message }}
}
}
}