refactor: db/index.ts in types, schema und seeds aufgeteilt
- types.ts: alle Interfaces und FeedbackRating-Typ - schema.ts: Dexie-Klasse + Migrationsversionen - seeds/assignmentTypes.ts: seedIfEmpty() - seeds/feedbackStructure.ts: seedFeedbackStructureIfEmpty() - seeds/migrations.ts: seedCriterionMappingsIfEmpty() + migrateToUnifiedCriteriaIfNeeded() - index.ts: reiner Re-Export, alle bestehenden Imports bleiben kompatibel
This commit is contained in:
parent
97e3eeb8a6
commit
068c4cdcfd
610
src/db/index.ts
610
src/db/index.ts
|
|
@ -1,602 +1,8 @@
|
|||
import Dexie, { type Table } from 'dexie'
|
||||
|
||||
// ─── Bewertungs-Skala (einheitlich für Meetings und Feedback) ─────────────────
|
||||
|
||||
export type FeedbackRating =
|
||||
| 'na'
|
||||
| 'not_client_ready'
|
||||
| 'partially_client_ready'
|
||||
| 'nearly_client_ready'
|
||||
| 'client_ready'
|
||||
|
||||
// ─── Konfiguration ───────────────────────────────────────────────────────────
|
||||
|
||||
export interface Category {
|
||||
id?: number
|
||||
name: string
|
||||
order: number
|
||||
}
|
||||
|
||||
export interface Criterion {
|
||||
id?: number
|
||||
categoryId: number
|
||||
name: string
|
||||
description: string
|
||||
weight: number
|
||||
order: number
|
||||
}
|
||||
|
||||
// ─── Assignment-Typen & Phase-Templates ──────────────────────────────────────
|
||||
|
||||
export type PhaseKey = string
|
||||
|
||||
export interface AssignmentType {
|
||||
id?: number
|
||||
name: string
|
||||
defaultPhaseKeys: PhaseKey[]
|
||||
criteriaIds?: number[] // FeedbackCategory IDs; leer = alle Kategorien anzeigen
|
||||
}
|
||||
|
||||
export interface PhaseTemplate {
|
||||
id?: number
|
||||
assignmentTypeId: number
|
||||
phaseKey: PhaseKey
|
||||
label: string
|
||||
withWhom: 'lead' | 'client'
|
||||
isRepeatable: boolean
|
||||
hasConversation: boolean
|
||||
hasAssessment: boolean
|
||||
criteriaIds: number[]
|
||||
order: number
|
||||
}
|
||||
|
||||
// ─── Stammdaten ───────────────────────────────────────────────────────────────
|
||||
|
||||
export interface Group {
|
||||
id?: number
|
||||
name: string
|
||||
cohortYear: number
|
||||
}
|
||||
|
||||
export interface Consultant {
|
||||
id?: number
|
||||
groupId: number
|
||||
firstName: string
|
||||
lastName: string
|
||||
}
|
||||
|
||||
// ─── Assignments ─────────────────────────────────────────────────────────────
|
||||
|
||||
export interface Assignment {
|
||||
id?: number
|
||||
title: string
|
||||
client: string
|
||||
assignmentTypeId: number
|
||||
instituteeIds: number[]
|
||||
leadName: string
|
||||
status: 'active' | 'done'
|
||||
createdAt: string
|
||||
assignmentNumber?: string // Offizielle Assignment-Nummer aus Systemsystemen
|
||||
description?: string
|
||||
instituteeEnrollments?: Array<{
|
||||
instituteeId: number
|
||||
from?: string // ISO date string
|
||||
to?: string // ISO date string
|
||||
}>
|
||||
}
|
||||
|
||||
// ─── Meeting-Instanzen ────────────────────────────────────────────────────────
|
||||
|
||||
export interface MeetingInstance {
|
||||
id?: number
|
||||
assignmentId: number
|
||||
phaseTemplateId: number
|
||||
phaseIndex: number
|
||||
date: string
|
||||
status: 'planned' | 'active' | 'done'
|
||||
generalNotes: string
|
||||
deletedAt?: string
|
||||
}
|
||||
|
||||
// ─── Gesprächs-Zeitleiste ─────────────────────────────────────────────────────
|
||||
|
||||
export interface ConversationEntry {
|
||||
id?: number
|
||||
meetingInstanceId: number
|
||||
instituteeId: number
|
||||
sequenceIndex: number
|
||||
note: string
|
||||
fillerCount: number
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
// criteriaId here now holds a FeedbackCategory.id (unified criteria system)
|
||||
export interface ConversationSkillScore {
|
||||
id?: number
|
||||
conversationEntryId: number
|
||||
criteriaId: number // → FeedbackCategory.id
|
||||
score: FeedbackRating | null
|
||||
}
|
||||
|
||||
// ─── Gesamtbewertung ─────────────────────────────────────────────────────────
|
||||
|
||||
// criteriaId here now holds a FeedbackCategory.id (unified criteria system)
|
||||
export interface Assessment {
|
||||
id?: number
|
||||
meetingInstanceId: number
|
||||
instituteeId: number
|
||||
criteriaId: number // → FeedbackCategory.id
|
||||
score: FeedbackRating | null
|
||||
note: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
// ─── Feedback-Entwurf (Legacy, bleibt erhalten) ──────────────────────────────
|
||||
|
||||
export interface FeedbackDraft {
|
||||
id?: number
|
||||
assignmentId: number
|
||||
instituteeId: number
|
||||
content: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
// ─── Strukturiertes Assignment-Feedback ───────────────────────────────────────
|
||||
|
||||
export interface FeedbackDimension {
|
||||
id?: number
|
||||
name: string
|
||||
order: number
|
||||
}
|
||||
|
||||
export interface FeedbackCategory {
|
||||
id?: number
|
||||
dimensionId: number
|
||||
name: string
|
||||
order: number
|
||||
}
|
||||
|
||||
export interface FeedbackCriterionItem {
|
||||
id?: number
|
||||
categoryId: number
|
||||
name: string
|
||||
weight?: number // Gewichtung, default 1
|
||||
order: number
|
||||
}
|
||||
|
||||
export interface AssignmentFeedback {
|
||||
id?: number
|
||||
assignmentId: number
|
||||
instituteeId: number
|
||||
status: 'draft' | 'final'
|
||||
aiModel: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface FeedbackCategoryRating {
|
||||
id?: number
|
||||
assignmentFeedbackId: number
|
||||
feedbackCategoryId: number
|
||||
rating: FeedbackRating
|
||||
}
|
||||
|
||||
export interface FeedbackDimensionText {
|
||||
id?: number
|
||||
assignmentFeedbackId: number
|
||||
feedbackDimensionId: number
|
||||
achievements: string
|
||||
developmentNeeds: string
|
||||
}
|
||||
|
||||
// Mapping: Assessment-Kriterium → Feedback-Kategorie (n:m, Legacy-Kompatibilität)
|
||||
export interface CriterionCategoryMapping {
|
||||
id?: number
|
||||
criterionId: number
|
||||
feedbackCategoryId: number
|
||||
}
|
||||
|
||||
// Beschreibungstext pro FeedbackCriterionItem und Bewertungsstufe
|
||||
export interface CriterionLevelDescription {
|
||||
id?: number
|
||||
criterionItemId: number // → FeedbackCriterionItem.id
|
||||
rating: FeedbackRating
|
||||
text: string
|
||||
}
|
||||
|
||||
// ─── Dexie DB ────────────────────────────────────────────────────────────────
|
||||
|
||||
class AssignmentMonitorDB extends Dexie {
|
||||
categories!: Table<Category>
|
||||
criteria!: Table<Criterion>
|
||||
assignmentTypes!: Table<AssignmentType>
|
||||
phaseTemplates!: Table<PhaseTemplate>
|
||||
groups!: Table<Group>
|
||||
consultants!: Table<Consultant>
|
||||
assignments!: Table<Assignment>
|
||||
meetingInstances!: Table<MeetingInstance>
|
||||
conversationEntries!: Table<ConversationEntry>
|
||||
conversationSkillScores!: Table<ConversationSkillScore>
|
||||
assessments!: Table<Assessment>
|
||||
feedbackDrafts!: Table<FeedbackDraft>
|
||||
feedbackDimensions!: Table<FeedbackDimension>
|
||||
feedbackCategories!: Table<FeedbackCategory>
|
||||
feedbackCriterionItems!: Table<FeedbackCriterionItem>
|
||||
assignmentFeedbacks!: Table<AssignmentFeedback>
|
||||
feedbackCategoryRatings!: Table<FeedbackCategoryRating>
|
||||
feedbackDimensionTexts!: Table<FeedbackDimensionText>
|
||||
criterionCategoryMappings!: Table<CriterionCategoryMapping>
|
||||
criterionLevelDescriptions!: Table<CriterionLevelDescription>
|
||||
|
||||
constructor() {
|
||||
super('AssignmentMonitorDB')
|
||||
this.version(1).stores({
|
||||
categories: '++id, order',
|
||||
criteria: '++id, categoryId, order',
|
||||
groups: '++id',
|
||||
consultants: '++id, groupId',
|
||||
meetings: '++id, date, status, groupId',
|
||||
assessments: '++id, meetingId, consultantId, criteriaId',
|
||||
consultantNotes: '++id, meetingId, consultantId',
|
||||
})
|
||||
this.version(2).stores({
|
||||
categories: '++id, order',
|
||||
criteria: '++id, categoryId, order',
|
||||
assignmentTypes: '++id',
|
||||
phaseTemplates: '++id, assignmentTypeId, order',
|
||||
groups: '++id',
|
||||
consultants: '++id, groupId',
|
||||
assignments: '++id, status, createdAt',
|
||||
meetingInstances: '++id, assignmentId, phaseTemplateId',
|
||||
conversationEntries: '++id, meetingInstanceId, instituteeId, sequenceIndex',
|
||||
conversationSkillScores: '++id, conversationEntryId, criteriaId',
|
||||
assessments: '++id, meetingInstanceId, instituteeId, criteriaId',
|
||||
feedbackDrafts: '++id, assignmentId, instituteeId',
|
||||
// v1 tables no longer used but kept for safe migration
|
||||
meetings: null,
|
||||
consultantNotes: null,
|
||||
})
|
||||
this.version(3).stores({
|
||||
categories: '++id, order',
|
||||
criteria: '++id, categoryId, order',
|
||||
assignmentTypes: '++id',
|
||||
phaseTemplates: '++id, assignmentTypeId, order',
|
||||
groups: '++id',
|
||||
consultants: '++id, groupId',
|
||||
assignments: '++id, status, createdAt',
|
||||
meetingInstances: '++id, assignmentId, phaseTemplateId',
|
||||
conversationEntries: '++id, meetingInstanceId, instituteeId, sequenceIndex',
|
||||
conversationSkillScores: '++id, conversationEntryId, criteriaId',
|
||||
assessments: '++id, meetingInstanceId, instituteeId, criteriaId',
|
||||
feedbackDrafts: '++id, assignmentId, instituteeId',
|
||||
feedbackDimensions: '++id, order',
|
||||
feedbackCategories: '++id, dimensionId, order',
|
||||
feedbackCriterionItems: '++id, categoryId, order',
|
||||
assignmentFeedbacks: '++id, assignmentId, instituteeId',
|
||||
feedbackCategoryRatings: '++id, assignmentFeedbackId, feedbackCategoryId',
|
||||
feedbackDimensionTexts: '++id, assignmentFeedbackId, feedbackDimensionId',
|
||||
meetings: null,
|
||||
consultantNotes: null,
|
||||
})
|
||||
this.version(4).stores({
|
||||
categories: '++id, order',
|
||||
criteria: '++id, categoryId, order',
|
||||
assignmentTypes: '++id',
|
||||
phaseTemplates: '++id, assignmentTypeId, order',
|
||||
groups: '++id',
|
||||
consultants: '++id, groupId',
|
||||
assignments: '++id, status, createdAt',
|
||||
meetingInstances: '++id, assignmentId, phaseTemplateId',
|
||||
conversationEntries: '++id, meetingInstanceId, instituteeId, sequenceIndex',
|
||||
conversationSkillScores: '++id, conversationEntryId, criteriaId',
|
||||
assessments: '++id, meetingInstanceId, instituteeId, criteriaId',
|
||||
feedbackDrafts: '++id, assignmentId, instituteeId',
|
||||
feedbackDimensions: '++id, order',
|
||||
feedbackCategories: '++id, dimensionId, order',
|
||||
feedbackCriterionItems: '++id, categoryId, order',
|
||||
assignmentFeedbacks: '++id, assignmentId, instituteeId',
|
||||
feedbackCategoryRatings: '++id, assignmentFeedbackId, feedbackCategoryId',
|
||||
feedbackDimensionTexts: '++id, assignmentFeedbackId, feedbackDimensionId',
|
||||
criterionCategoryMappings: '++id, criterionId, feedbackCategoryId',
|
||||
meetings: null,
|
||||
consultantNotes: null,
|
||||
})
|
||||
this.version(5).stores({
|
||||
categories: '++id, order',
|
||||
criteria: '++id, categoryId, order',
|
||||
assignmentTypes: '++id',
|
||||
phaseTemplates: '++id, assignmentTypeId, order',
|
||||
groups: '++id',
|
||||
consultants: '++id, groupId',
|
||||
assignments: '++id, status, createdAt',
|
||||
meetingInstances: '++id, assignmentId, phaseTemplateId',
|
||||
conversationEntries: '++id, meetingInstanceId, instituteeId, sequenceIndex',
|
||||
conversationSkillScores: '++id, conversationEntryId, criteriaId',
|
||||
assessments: '++id, meetingInstanceId, instituteeId, criteriaId',
|
||||
feedbackDrafts: '++id, assignmentId, instituteeId',
|
||||
feedbackDimensions: '++id, order',
|
||||
feedbackCategories: '++id, dimensionId, order',
|
||||
feedbackCriterionItems: '++id, categoryId, order',
|
||||
assignmentFeedbacks: '++id, assignmentId, instituteeId',
|
||||
feedbackCategoryRatings: '++id, assignmentFeedbackId, feedbackCategoryId',
|
||||
feedbackDimensionTexts: '++id, assignmentFeedbackId, feedbackDimensionId',
|
||||
criterionCategoryMappings: '++id, criterionId, feedbackCategoryId',
|
||||
criterionLevelDescriptions: '++id, criterionItemId, rating',
|
||||
meetings: null,
|
||||
consultantNotes: null,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const db = new AssignmentMonitorDB()
|
||||
|
||||
// ─── Seed ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export async function seedIfEmpty() {
|
||||
const count = await db.assignmentTypes.count()
|
||||
if (count > 0) return
|
||||
|
||||
// ── Gemeinsame Kategorien & Kriterien ────────────────────────────────────
|
||||
const catKomm = await db.categories.add({ name: 'Kommunikation', order: 1 })
|
||||
const catFach = await db.categories.add({ name: 'Fachkompetenz', order: 2 })
|
||||
const catKunde = await db.categories.add({ name: 'Kundenorientierung', order: 3 })
|
||||
const catSelf = await db.categories.add({ name: 'Selbstorganisation', order: 4 })
|
||||
const catCase = await db.categories.add({ name: 'Case-Kompetenz', order: 5 })
|
||||
|
||||
const kritKlarheit = await db.criteria.add({ categoryId: catKomm, name: 'Klarheit & Struktur', description: 'Klare, strukturierte Aussagen', weight: 2, order: 1 })
|
||||
const kritUeberzeug = await db.criteria.add({ categoryId: catKomm, name: 'Überzeugungskraft', description: 'Argumente überzeugend präsentieren', weight: 2, order: 2 })
|
||||
const kritZuhoeren = await db.criteria.add({ categoryId: catKomm, name: 'Aktives Zuhören', description: 'Eingehen auf Gesprächspartner', weight: 1, order: 3 })
|
||||
const kritFragen = await db.criteria.add({ categoryId: catKomm, name: 'Umgang mit Fragen', description: 'Reaktion auf Kunden-Fragen', weight: 2, order: 4 })
|
||||
const kritTiefe = await db.criteria.add({ categoryId: catFach, name: 'Fachliche Tiefe', description: 'Inhaltliche Korrektheit und Tiefe', weight: 3, order: 1 })
|
||||
const kritProblem = await db.criteria.add({ categoryId: catFach, name: 'Problemlösung', description: 'Analytisches Vorgehen, Hypothesen', weight: 3, order: 2 })
|
||||
const kritStakeholder = await db.criteria.add({ categoryId: catKunde, name: 'Stakeholder-Verständnis', description: 'Erkennt Bedürfnisse und Erwartungen', weight: 2, order: 1 })
|
||||
const kritEmpathie = await db.criteria.add({ categoryId: catKunde, name: 'Empathie', description: 'Einfühlungsvermögen im Gespräch', weight: 1, order: 2 })
|
||||
const kritVorb = await db.criteria.add({ categoryId: catSelf, name: 'Vorbereitung', description: 'Zeigt gute Meeting-Vorbereitung', weight: 2, order: 1 })
|
||||
const kritInitiative = await db.criteria.add({ categoryId: catSelf, name: 'Eigeninitiative', description: 'Handelt proaktiv ohne Aufforderung', weight: 2, order: 2 })
|
||||
const kritStruktur = await db.criteria.add({ categoryId: catCase, name: 'Problemstrukturierung', description: 'Systematische Zerlegung des Problems', weight: 3, order: 1 })
|
||||
const kritHypo = await db.criteria.add({ categoryId: catCase, name: 'Hypothesenbildung', description: 'Klare, testbare Hypothesen', weight: 3, order: 2 })
|
||||
const kritAnalyse = await db.criteria.add({ categoryId: catCase, name: 'Rechnen & Analyse', description: 'Datenbasiertes Vorgehen, Schätzfähigkeit', weight: 2, order: 3 })
|
||||
const kritZeitmanage = await db.criteria.add({ categoryId: catCase, name: 'Zeitmanagement', description: 'Effizienter Umgang mit der Case-Zeit', weight: 2, order: 4 })
|
||||
|
||||
// ── Hilfsfunktion: PhaseTemplate anlegen ────────────────────────────────
|
||||
const addPhase = (
|
||||
assignmentTypeId: number,
|
||||
phaseKey: PhaseKey,
|
||||
label: string,
|
||||
withWhom: 'lead' | 'client',
|
||||
opts: { repeatable?: boolean; conv?: boolean; assess?: boolean; criteriaIds?: number[]; order: number }
|
||||
) => db.phaseTemplates.add({
|
||||
assignmentTypeId,
|
||||
phaseKey,
|
||||
label,
|
||||
withWhom,
|
||||
isRepeatable: opts.repeatable ?? false,
|
||||
hasConversation: opts.conv ?? false,
|
||||
hasAssessment: opts.assess ?? false,
|
||||
criteriaIds: opts.criteriaIds ?? [],
|
||||
order: opts.order,
|
||||
})
|
||||
|
||||
// ── AssignmentType 1: Generisch ──────────────────────────────────────────
|
||||
const genericId = await db.assignmentTypes.add({
|
||||
name: 'Generisch',
|
||||
defaultPhaseKeys: ['preBriefing', 'briefing', 'alignmentCall', 'preDelivery', 'delivery', 'feedbackCallClient', 'feedbackCallLead'],
|
||||
})
|
||||
const genericCriteria = [kritKlarheit, kritUeberzeug, kritZuhoeren, kritTiefe, kritProblem, kritStakeholder, kritEmpathie, kritVorb, kritInitiative]
|
||||
await addPhase(genericId, 'preBriefing', 'Pre-Briefing (Lead)', 'lead', { order: 1 })
|
||||
await addPhase(genericId, 'briefing', 'Briefing (Kunde)', 'client', { conv: true, assess: true, criteriaIds: genericCriteria, order: 2 })
|
||||
await addPhase(genericId, 'alignmentCall', 'Alignment Call (Kunde)', 'client', { repeatable: true, conv: true, assess: true, criteriaIds: genericCriteria, order: 3 })
|
||||
await addPhase(genericId, 'preDelivery', 'Pre-Delivery (Lead)', 'lead', { order: 4 })
|
||||
await addPhase(genericId, 'delivery', 'Delivery (Kunde)', 'client', { conv: true, assess: true, criteriaIds: genericCriteria, order: 5 })
|
||||
await addPhase(genericId, 'feedbackCallClient','Feedback Call (Kunde)', 'client', { conv: true, assess: true, criteriaIds: genericCriteria, order: 6 })
|
||||
await addPhase(genericId, 'feedbackCallLead', 'Feedback Call (Lead)', 'lead', { order: 7 })
|
||||
|
||||
// ── AssignmentType 2: Case Interview ────────────────────────────────────
|
||||
const caseId = await db.assignmentTypes.add({
|
||||
name: 'Case Interview',
|
||||
defaultPhaseKeys: ['preBriefing', 'briefing', 'delivery', 'feedbackCallClient', 'feedbackCallLead'],
|
||||
})
|
||||
const caseCriteria = [kritStruktur, kritHypo, kritAnalyse, kritKlarheit, kritZeitmanage, kritUeberzeug, kritFragen]
|
||||
await addPhase(caseId, 'preBriefing', 'Pre-Briefing (Lead)', 'lead', { order: 1 })
|
||||
await addPhase(caseId, 'briefing', 'Case Briefing (Kunde)', 'client', { conv: true, assess: true, criteriaIds: caseCriteria, order: 2 })
|
||||
await addPhase(caseId, 'delivery', 'Case Delivery (Kunde)', 'client', { conv: true, assess: true, criteriaIds: caseCriteria, order: 3 })
|
||||
await addPhase(caseId, 'feedbackCallClient','Feedback Call (Kunde)', 'client', { conv: true, assess: true, criteriaIds: caseCriteria, order: 4 })
|
||||
await addPhase(caseId, 'feedbackCallLead', 'Feedback Call (Lead)', 'lead', { order: 5 })
|
||||
|
||||
// ── AssignmentType 3: Stakeholder Meeting / Pitch ────────────────────────
|
||||
const pitchId = await db.assignmentTypes.add({
|
||||
name: 'Stakeholder Meeting / Pitch',
|
||||
defaultPhaseKeys: ['preBriefing', 'briefing', 'alignmentCall', 'preDelivery', 'delivery', 'feedbackCallClient', 'feedbackCallLead'],
|
||||
})
|
||||
const pitchCriteria = [kritVorb, kritKlarheit, kritUeberzeug, kritStakeholder, kritFragen, kritEmpathie, kritInitiative]
|
||||
await addPhase(pitchId, 'preBriefing', 'Pre-Briefing (Lead)', 'lead', { order: 1 })
|
||||
await addPhase(pitchId, 'briefing', 'Briefing (Kunde)', 'client', { conv: true, assess: true, criteriaIds: pitchCriteria, order: 2 })
|
||||
await addPhase(pitchId, 'alignmentCall', 'Alignment Call (Kunde)', 'client', { repeatable: true, conv: true, assess: true, criteriaIds: pitchCriteria, order: 3 })
|
||||
await addPhase(pitchId, 'preDelivery', 'Pre-Delivery (Lead)', 'lead', { order: 4 })
|
||||
await addPhase(pitchId, 'delivery', 'Pitch / Delivery (Kunde)', 'client', { conv: true, assess: true, criteriaIds: pitchCriteria, order: 5 })
|
||||
await addPhase(pitchId, 'feedbackCallClient','Feedback Call (Kunde)', 'client', { conv: true, assess: true, criteriaIds: pitchCriteria, order: 6 })
|
||||
await addPhase(pitchId, 'feedbackCallLead', 'Feedback Call (Lead)', 'lead', { order: 7 })
|
||||
}
|
||||
|
||||
// ─── Seed: Feedback-Dimensionsstruktur ───────────────────────────────────────
|
||||
|
||||
export async function seedFeedbackStructureIfEmpty() {
|
||||
const count = await db.feedbackDimensions.count()
|
||||
if (count > 0) return
|
||||
|
||||
type CatSeed = { name: string; items: string[] }
|
||||
type DimSeed = { name: string; cats: CatSeed[] }
|
||||
|
||||
const dimensions: DimSeed[] = [
|
||||
{
|
||||
name: 'Mindset, Commitment, Collaboration',
|
||||
cats: [
|
||||
{ name: 'Persönliche Einstellung', items: ['Ruhig bei Stress', 'Offen für neue Themen', 'Auftreten / Ausstrahlung', 'Pro aktiv', 'Lösungsorientiert'] },
|
||||
{ name: 'Professionalität', items: ['Pünktlichkeit', 'Professionell bei informellen Gesprächen', 'Adressatengerecht', 'Hohes Maß an Selbststeuerung', 'Klarheit', 'Zuverlässigkeit'] },
|
||||
{ name: 'Commitment', items: ['Offen für spontane Änderungen', 'Selbständiger Lösungsansatz', 'Pro aktive Ideen', 'Verantwortungsbewusst', 'Hilfsbereit', 'Teamorientiert'] },
|
||||
{ name: 'Kommunikationsfähigkeit', items: ['To the point', 'Recap-Fähigkeit', 'Active Listening', 'Souveränität'] },
|
||||
{ name: 'Zusammenarbeit', items: ['Regelmäßige Abstimmung', 'Unterstützung bei Bedarf', 'Know-How Sharing', 'Starkes Teamverhalten', 'Verantwortung und Vertrauen', 'Inhaltlich unterstützend'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Running Our Projects',
|
||||
cats: [
|
||||
{ name: 'Ergebnisverantwortung', items: ['Fokussierung', 'Priorisierung (Impact Driven)', 'Machbarkeit / Umsetzbarkeit', 'Alternativvorschläge', 'Interne Abstimmung und Feedback'] },
|
||||
{ name: 'Arbeitsstil', items: ['Komplexitätsmanagement', 'Eigenverantwortung', 'Feedbackaffinität', 'Verantwortungsbewusst / vorausschauend', 'Selbstreflektiert, lösungsorientiert', 'Offen, initiativ, kollegial', 'Organisiert, kommunikationsfähig', 'Lernfähig, anpassungsbereit, flexibel'] },
|
||||
{ name: 'Kundenmanagement', items: ['Aktives Erwartungsmanagement', 'Selbstsicherheit und fachliche Überzeugung', 'Aktive Advisory', 'Realistisch', 'Kundenfokussiert'] },
|
||||
{ name: 'Lieferzeiten', items: ['Verlässlichkeit', 'Fortschrittskontrolle und Planadaption', 'Aktives Scoping', 'Meetingsvorbereitung', 'Transparent'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Growing our People',
|
||||
cats: [
|
||||
{ name: 'Networking', items: ['Cross-Collaboration', 'Network Growth', 'Network Utilization'] },
|
||||
{ name: 'Learning und Development', items: ['Kritikfähigkeit', 'Wissensaufbau (Kompetenzausstrahlung)', 'Umsetzungsbereitschaft Feedback'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Functional & Technical Skills',
|
||||
cats: [
|
||||
{ name: 'Qualität des Inhalts', items: ['Belastbarkeit', 'Nachvollziehbarkeit', 'Logische Storyline'] },
|
||||
{ name: 'Inhaltliche Vollständigkeit', items: ['Wesentliche Elemente vorhanden', 'Realitätsnähe', 'Nutzenfokus'] },
|
||||
{ name: 'Qualität der Unterlage', items: ['Design nach Standardvorgaben', 'Sprachliche Präzision', 'Visualisierungen unterstützen Kernbotschaften'] },
|
||||
{ name: 'Prozesstreue', items: ['Kenne und befolge Prozesse und Vorgaben', 'Nachvollziehbarkeit und Transparenz', 'Qualitätschecks'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Innovation und Asset Development',
|
||||
cats: [
|
||||
{ name: 'Innovation', items: ['Aktiver Vorschlag für Verbesserungen', 'Neue Ideen zur Verbesserung', 'Nutze aktuellen Wissensstand', 'Liefere über den Kundenerwartungen'] },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
for (let di = 0; di < dimensions.length; di++) {
|
||||
const dim = dimensions[di]
|
||||
const dimId = await db.feedbackDimensions.add({ name: dim.name, order: di + 1 })
|
||||
for (let ci = 0; ci < dim.cats.length; ci++) {
|
||||
const cat = dim.cats[ci]
|
||||
const catId = await db.feedbackCategories.add({ dimensionId: dimId, name: cat.name, order: ci + 1 })
|
||||
for (let ii = 0; ii < cat.items.length; ii++) {
|
||||
await db.feedbackCriterionItems.add({ categoryId: catId, name: cat.items[ii], order: ii + 1 })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Seed: Kriterien-Mapping ──────────────────────────────────────────────────
|
||||
|
||||
export async function seedCriterionMappingsIfEmpty() {
|
||||
const count = await db.criterionCategoryMappings.count()
|
||||
if (count > 0) return
|
||||
|
||||
// Warte bis Kriterien und Feedback-Kategorien vorhanden sind
|
||||
const [crits, cats] = await Promise.all([
|
||||
db.criteria.toArray(),
|
||||
db.feedbackCategories.toArray(),
|
||||
])
|
||||
if (crits.length === 0 || cats.length === 0) return
|
||||
|
||||
const c = (name: string) => crits.find(x => x.name === name)?.id
|
||||
const f = (name: string) => cats.find(x => x.name === name)?.id
|
||||
|
||||
const mappings: [string, string][] = [
|
||||
// Kriterium → Feedback-Kategorie
|
||||
['Klarheit & Struktur', 'Kommunikationsfähigkeit'],
|
||||
['Klarheit & Struktur', 'Qualität des Inhalts'],
|
||||
['Überzeugungskraft', 'Kommunikationsfähigkeit'],
|
||||
['Überzeugungskraft', 'Kundenmanagement'],
|
||||
['Aktives Zuhören', 'Kommunikationsfähigkeit'],
|
||||
['Umgang mit Fragen', 'Kommunikationsfähigkeit'],
|
||||
['Umgang mit Fragen', 'Kundenmanagement'],
|
||||
['Fachliche Tiefe', 'Qualität des Inhalts'],
|
||||
['Fachliche Tiefe', 'Inhaltliche Vollständigkeit'],
|
||||
['Problemlösung', 'Ergebnisverantwortung'],
|
||||
['Problemlösung', 'Arbeitsstil'],
|
||||
['Stakeholder-Verständnis', 'Kundenmanagement'],
|
||||
['Stakeholder-Verständnis', 'Persönliche Einstellung'],
|
||||
['Empathie', 'Persönliche Einstellung'],
|
||||
['Empathie', 'Kommunikationsfähigkeit'],
|
||||
['Vorbereitung', 'Professionalität'],
|
||||
['Vorbereitung', 'Lieferzeiten'],
|
||||
['Eigeninitiative', 'Commitment'],
|
||||
['Eigeninitiative', 'Persönliche Einstellung'],
|
||||
['Problemstrukturierung', 'Ergebnisverantwortung'],
|
||||
['Problemstrukturierung', 'Qualität des Inhalts'],
|
||||
['Hypothesenbildung', 'Ergebnisverantwortung'],
|
||||
['Hypothesenbildung', 'Arbeitsstil'],
|
||||
['Rechnen & Analyse', 'Qualität des Inhalts'],
|
||||
['Rechnen & Analyse', 'Inhaltliche Vollständigkeit'],
|
||||
['Zeitmanagement', 'Lieferzeiten'],
|
||||
['Zeitmanagement', 'Arbeitsstil'],
|
||||
]
|
||||
|
||||
for (const [critName, catName] of mappings) {
|
||||
const criterionId = c(critName)
|
||||
const feedbackCategoryId = f(catName)
|
||||
if (criterionId && feedbackCategoryId) {
|
||||
await db.criterionCategoryMappings.add({ criterionId, feedbackCategoryId })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Migration: Unified Criteria System ──────────────────────────────────────
|
||||
// Löscht nur Bewertungs-Scores (noch nicht befüllt), nie Protokoll-Texte.
|
||||
// Verknüpft PhaseTemplates mit FeedbackCategory-IDs statt alten Criterion-IDs.
|
||||
|
||||
export async function migrateToUnifiedCriteriaIfNeeded() {
|
||||
// Prüfen ob Migration schon gelaufen: erste PhaseTemplate mit criteriaIds die
|
||||
// auf eine FeedbackCategory zeigen (vs. alte Criterion-IDs die niedrig sein werden)
|
||||
const cats = await db.feedbackCategories.toArray()
|
||||
if (cats.length === 0) return // Feedback-Struktur noch nicht geseeded
|
||||
|
||||
const templates = await db.phaseTemplates.toArray()
|
||||
if (templates.length === 0) return
|
||||
|
||||
// Prüfen ob irgendeine Template-criteriaId in den FeedbackCategory-IDs liegt
|
||||
const catIds = new Set(cats.map(c => c.id!))
|
||||
const firstWithIds = templates.find(t => t.criteriaIds.length > 0)
|
||||
if (firstWithIds && catIds.has(firstWithIds.criteriaIds[0])) return // schon migriert
|
||||
|
||||
// Kategorien nach Name nachschlagen
|
||||
const cat = (name: string) => cats.find(c => c.name === name)?.id
|
||||
|
||||
// Scores löschen (keine Textinhalte, nur Bewertungszahlen)
|
||||
await db.conversationSkillScores.clear()
|
||||
await db.assessments.clear()
|
||||
|
||||
// Phase-Templates mit neuen Kategorie-IDs befüllen
|
||||
// Kategorien-Sets pro Assignment-Typ und Phasentyp
|
||||
const genericCats = [
|
||||
cat('Kommunikationsfähigkeit'), cat('Persönliche Einstellung'),
|
||||
cat('Kundenmanagement'), cat('Ergebnisverantwortung'), cat('Qualität des Inhalts'),
|
||||
].filter(Boolean) as number[]
|
||||
|
||||
const caseCats = [
|
||||
cat('Kommunikationsfähigkeit'), cat('Ergebnisverantwortung'),
|
||||
cat('Arbeitsstil'), cat('Qualität des Inhalts'), cat('Inhaltliche Vollständigkeit'),
|
||||
].filter(Boolean) as number[]
|
||||
|
||||
const pitchCats = [
|
||||
cat('Kommunikationsfähigkeit'), cat('Persönliche Einstellung'),
|
||||
cat('Kundenmanagement'), cat('Ergebnisverantwortung'),
|
||||
cat('Qualität des Inhalts'), cat('Qualität der Unterlage'),
|
||||
].filter(Boolean) as number[]
|
||||
|
||||
const types = await db.assignmentTypes.toArray()
|
||||
for (const tmpl of templates) {
|
||||
if (!tmpl.hasAssessment && !tmpl.hasConversation) {
|
||||
await db.phaseTemplates.update(tmpl.id!, { criteriaIds: [] })
|
||||
continue
|
||||
}
|
||||
const aType = types.find(t => t.id === tmpl.assignmentTypeId)
|
||||
let newIds: number[]
|
||||
if (aType?.name === 'Case Interview') {
|
||||
newIds = caseCats
|
||||
} else if (aType?.name === 'Stakeholder Meeting / Pitch') {
|
||||
newIds = pitchCats
|
||||
} else {
|
||||
newIds = genericCats
|
||||
}
|
||||
await db.phaseTemplates.update(tmpl.id!, { criteriaIds: newIds })
|
||||
}
|
||||
}
|
||||
export * from './types'
|
||||
export { db } from './schema'
|
||||
export {
|
||||
seedIfEmpty,
|
||||
seedFeedbackStructureIfEmpty,
|
||||
seedCriterionMappingsIfEmpty,
|
||||
migrateToUnifiedCriteriaIfNeeded,
|
||||
} from './seeds'
|
||||
|
|
|
|||
135
src/db/schema.ts
Normal file
135
src/db/schema.ts
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
import Dexie, { type Table } from 'dexie'
|
||||
import type {
|
||||
Category, Criterion,
|
||||
AssignmentType, PhaseTemplate,
|
||||
Group, Consultant,
|
||||
Assignment, MeetingInstance,
|
||||
ConversationEntry, ConversationSkillScore,
|
||||
Assessment, FeedbackDraft,
|
||||
FeedbackDimension, FeedbackCategory, FeedbackCriterionItem,
|
||||
AssignmentFeedback, FeedbackCategoryRating, FeedbackDimensionText,
|
||||
CriterionCategoryMapping, CriterionLevelDescription,
|
||||
} from './types'
|
||||
|
||||
class AssignmentMonitorDB extends Dexie {
|
||||
categories!: Table<Category>
|
||||
criteria!: Table<Criterion>
|
||||
assignmentTypes!: Table<AssignmentType>
|
||||
phaseTemplates!: Table<PhaseTemplate>
|
||||
groups!: Table<Group>
|
||||
consultants!: Table<Consultant>
|
||||
assignments!: Table<Assignment>
|
||||
meetingInstances!: Table<MeetingInstance>
|
||||
conversationEntries!: Table<ConversationEntry>
|
||||
conversationSkillScores!: Table<ConversationSkillScore>
|
||||
assessments!: Table<Assessment>
|
||||
feedbackDrafts!: Table<FeedbackDraft>
|
||||
feedbackDimensions!: Table<FeedbackDimension>
|
||||
feedbackCategories!: Table<FeedbackCategory>
|
||||
feedbackCriterionItems!: Table<FeedbackCriterionItem>
|
||||
assignmentFeedbacks!: Table<AssignmentFeedback>
|
||||
feedbackCategoryRatings!: Table<FeedbackCategoryRating>
|
||||
feedbackDimensionTexts!: Table<FeedbackDimensionText>
|
||||
criterionCategoryMappings!: Table<CriterionCategoryMapping>
|
||||
criterionLevelDescriptions!: Table<CriterionLevelDescription>
|
||||
|
||||
constructor() {
|
||||
super('AssignmentMonitorDB')
|
||||
this.version(1).stores({
|
||||
categories: '++id, order',
|
||||
criteria: '++id, categoryId, order',
|
||||
groups: '++id',
|
||||
consultants: '++id, groupId',
|
||||
meetings: '++id, date, status, groupId',
|
||||
assessments: '++id, meetingId, consultantId, criteriaId',
|
||||
consultantNotes: '++id, meetingId, consultantId',
|
||||
})
|
||||
this.version(2).stores({
|
||||
categories: '++id, order',
|
||||
criteria: '++id, categoryId, order',
|
||||
assignmentTypes: '++id',
|
||||
phaseTemplates: '++id, assignmentTypeId, order',
|
||||
groups: '++id',
|
||||
consultants: '++id, groupId',
|
||||
assignments: '++id, status, createdAt',
|
||||
meetingInstances: '++id, assignmentId, phaseTemplateId',
|
||||
conversationEntries: '++id, meetingInstanceId, instituteeId, sequenceIndex',
|
||||
conversationSkillScores: '++id, conversationEntryId, criteriaId',
|
||||
assessments: '++id, meetingInstanceId, instituteeId, criteriaId',
|
||||
feedbackDrafts: '++id, assignmentId, instituteeId',
|
||||
meetings: null,
|
||||
consultantNotes: null,
|
||||
})
|
||||
this.version(3).stores({
|
||||
categories: '++id, order',
|
||||
criteria: '++id, categoryId, order',
|
||||
assignmentTypes: '++id',
|
||||
phaseTemplates: '++id, assignmentTypeId, order',
|
||||
groups: '++id',
|
||||
consultants: '++id, groupId',
|
||||
assignments: '++id, status, createdAt',
|
||||
meetingInstances: '++id, assignmentId, phaseTemplateId',
|
||||
conversationEntries: '++id, meetingInstanceId, instituteeId, sequenceIndex',
|
||||
conversationSkillScores: '++id, conversationEntryId, criteriaId',
|
||||
assessments: '++id, meetingInstanceId, instituteeId, criteriaId',
|
||||
feedbackDrafts: '++id, assignmentId, instituteeId',
|
||||
feedbackDimensions: '++id, order',
|
||||
feedbackCategories: '++id, dimensionId, order',
|
||||
feedbackCriterionItems: '++id, categoryId, order',
|
||||
assignmentFeedbacks: '++id, assignmentId, instituteeId',
|
||||
feedbackCategoryRatings: '++id, assignmentFeedbackId, feedbackCategoryId',
|
||||
feedbackDimensionTexts: '++id, assignmentFeedbackId, feedbackDimensionId',
|
||||
meetings: null,
|
||||
consultantNotes: null,
|
||||
})
|
||||
this.version(4).stores({
|
||||
categories: '++id, order',
|
||||
criteria: '++id, categoryId, order',
|
||||
assignmentTypes: '++id',
|
||||
phaseTemplates: '++id, assignmentTypeId, order',
|
||||
groups: '++id',
|
||||
consultants: '++id, groupId',
|
||||
assignments: '++id, status, createdAt',
|
||||
meetingInstances: '++id, assignmentId, phaseTemplateId',
|
||||
conversationEntries: '++id, meetingInstanceId, instituteeId, sequenceIndex',
|
||||
conversationSkillScores: '++id, conversationEntryId, criteriaId',
|
||||
assessments: '++id, meetingInstanceId, instituteeId, criteriaId',
|
||||
feedbackDrafts: '++id, assignmentId, instituteeId',
|
||||
feedbackDimensions: '++id, order',
|
||||
feedbackCategories: '++id, dimensionId, order',
|
||||
feedbackCriterionItems: '++id, categoryId, order',
|
||||
assignmentFeedbacks: '++id, assignmentId, instituteeId',
|
||||
feedbackCategoryRatings: '++id, assignmentFeedbackId, feedbackCategoryId',
|
||||
feedbackDimensionTexts: '++id, assignmentFeedbackId, feedbackDimensionId',
|
||||
criterionCategoryMappings: '++id, criterionId, feedbackCategoryId',
|
||||
meetings: null,
|
||||
consultantNotes: null,
|
||||
})
|
||||
this.version(5).stores({
|
||||
categories: '++id, order',
|
||||
criteria: '++id, categoryId, order',
|
||||
assignmentTypes: '++id',
|
||||
phaseTemplates: '++id, assignmentTypeId, order',
|
||||
groups: '++id',
|
||||
consultants: '++id, groupId',
|
||||
assignments: '++id, status, createdAt',
|
||||
meetingInstances: '++id, assignmentId, phaseTemplateId',
|
||||
conversationEntries: '++id, meetingInstanceId, instituteeId, sequenceIndex',
|
||||
conversationSkillScores: '++id, conversationEntryId, criteriaId',
|
||||
assessments: '++id, meetingInstanceId, instituteeId, criteriaId',
|
||||
feedbackDrafts: '++id, assignmentId, instituteeId',
|
||||
feedbackDimensions: '++id, order',
|
||||
feedbackCategories: '++id, dimensionId, order',
|
||||
feedbackCriterionItems: '++id, categoryId, order',
|
||||
assignmentFeedbacks: '++id, assignmentId, instituteeId',
|
||||
feedbackCategoryRatings: '++id, assignmentFeedbackId, feedbackCategoryId',
|
||||
feedbackDimensionTexts: '++id, assignmentFeedbackId, feedbackDimensionId',
|
||||
criterionCategoryMappings: '++id, criterionId, feedbackCategoryId',
|
||||
criterionLevelDescriptions: '++id, criterionItemId, rating',
|
||||
meetings: null,
|
||||
consultantNotes: null,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const db = new AssignmentMonitorDB()
|
||||
87
src/db/seeds/assignmentTypes.ts
Normal file
87
src/db/seeds/assignmentTypes.ts
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import { db } from '../schema'
|
||||
import type { PhaseKey } from '../types'
|
||||
|
||||
export async function seedIfEmpty() {
|
||||
const count = await db.assignmentTypes.count()
|
||||
if (count > 0) return
|
||||
|
||||
// ── Gemeinsame Kategorien & Kriterien (Legacy-Tabellen für Evaluation.tsx) ──
|
||||
const catKomm = await db.categories.add({ name: 'Kommunikation', order: 1 })
|
||||
const catFach = await db.categories.add({ name: 'Fachkompetenz', order: 2 })
|
||||
const catKunde = await db.categories.add({ name: 'Kundenorientierung', order: 3 })
|
||||
const catSelf = await db.categories.add({ name: 'Selbstorganisation', order: 4 })
|
||||
const catCase = await db.categories.add({ name: 'Case-Kompetenz', order: 5 })
|
||||
|
||||
const kritKlarheit = await db.criteria.add({ categoryId: catKomm, name: 'Klarheit & Struktur', description: 'Klare, strukturierte Aussagen', weight: 2, order: 1 })
|
||||
const kritUeberzeug = await db.criteria.add({ categoryId: catKomm, name: 'Überzeugungskraft', description: 'Argumente überzeugend präsentieren', weight: 2, order: 2 })
|
||||
const kritZuhoeren = await db.criteria.add({ categoryId: catKomm, name: 'Aktives Zuhören', description: 'Eingehen auf Gesprächspartner', weight: 1, order: 3 })
|
||||
const kritFragen = await db.criteria.add({ categoryId: catKomm, name: 'Umgang mit Fragen', description: 'Reaktion auf Kunden-Fragen', weight: 2, order: 4 })
|
||||
const kritTiefe = await db.criteria.add({ categoryId: catFach, name: 'Fachliche Tiefe', description: 'Inhaltliche Korrektheit und Tiefe', weight: 3, order: 1 })
|
||||
const kritProblem = await db.criteria.add({ categoryId: catFach, name: 'Problemlösung', description: 'Analytisches Vorgehen, Hypothesen', weight: 3, order: 2 })
|
||||
const kritStakeholder = await db.criteria.add({ categoryId: catKunde, name: 'Stakeholder-Verständnis', description: 'Erkennt Bedürfnisse und Erwartungen', weight: 2, order: 1 })
|
||||
const kritEmpathie = await db.criteria.add({ categoryId: catKunde, name: 'Empathie', description: 'Einfühlungsvermögen im Gespräch', weight: 1, order: 2 })
|
||||
const kritVorb = await db.criteria.add({ categoryId: catSelf, name: 'Vorbereitung', description: 'Zeigt gute Meeting-Vorbereitung', weight: 2, order: 1 })
|
||||
const kritInitiative = await db.criteria.add({ categoryId: catSelf, name: 'Eigeninitiative', description: 'Handelt proaktiv ohne Aufforderung', weight: 2, order: 2 })
|
||||
const kritStruktur = await db.criteria.add({ categoryId: catCase, name: 'Problemstrukturierung', description: 'Systematische Zerlegung des Problems', weight: 3, order: 1 })
|
||||
const kritHypo = await db.criteria.add({ categoryId: catCase, name: 'Hypothesenbildung', description: 'Klare, testbare Hypothesen', weight: 3, order: 2 })
|
||||
const kritAnalyse = await db.criteria.add({ categoryId: catCase, name: 'Rechnen & Analyse', description: 'Datenbasiertes Vorgehen, Schätzfähigkeit', weight: 2, order: 3 })
|
||||
const kritZeitmanage = await db.criteria.add({ categoryId: catCase, name: 'Zeitmanagement', description: 'Effizienter Umgang mit der Case-Zeit', weight: 2, order: 4 })
|
||||
|
||||
const addPhase = (
|
||||
assignmentTypeId: number,
|
||||
phaseKey: PhaseKey,
|
||||
label: string,
|
||||
withWhom: 'lead' | 'client',
|
||||
opts: { repeatable?: boolean; conv?: boolean; assess?: boolean; criteriaIds?: number[]; order: number }
|
||||
) => db.phaseTemplates.add({
|
||||
assignmentTypeId,
|
||||
phaseKey,
|
||||
label,
|
||||
withWhom,
|
||||
isRepeatable: opts.repeatable ?? false,
|
||||
hasConversation: opts.conv ?? false,
|
||||
hasAssessment: opts.assess ?? false,
|
||||
criteriaIds: opts.criteriaIds ?? [],
|
||||
order: opts.order,
|
||||
})
|
||||
|
||||
// ── AssignmentType 1: Generisch ──────────────────────────────────────────
|
||||
const genericId = await db.assignmentTypes.add({
|
||||
name: 'Generisch',
|
||||
defaultPhaseKeys: ['preBriefing', 'briefing', 'alignmentCall', 'preDelivery', 'delivery', 'feedbackCallClient', 'feedbackCallLead'],
|
||||
})
|
||||
const genericCriteria = [kritKlarheit, kritUeberzeug, kritZuhoeren, kritTiefe, kritProblem, kritStakeholder, kritEmpathie, kritVorb, kritInitiative]
|
||||
await addPhase(genericId, 'preBriefing', 'Pre-Briefing (Lead)', 'lead', { order: 1 })
|
||||
await addPhase(genericId, 'briefing', 'Briefing (Kunde)', 'client', { conv: true, assess: true, criteriaIds: genericCriteria, order: 2 })
|
||||
await addPhase(genericId, 'alignmentCall', 'Alignment Call (Kunde)', 'client', { repeatable: true, conv: true, assess: true, criteriaIds: genericCriteria, order: 3 })
|
||||
await addPhase(genericId, 'preDelivery', 'Pre-Delivery (Lead)', 'lead', { order: 4 })
|
||||
await addPhase(genericId, 'delivery', 'Delivery (Kunde)', 'client', { conv: true, assess: true, criteriaIds: genericCriteria, order: 5 })
|
||||
await addPhase(genericId, 'feedbackCallClient', 'Feedback Call (Kunde)', 'client', { conv: true, assess: true, criteriaIds: genericCriteria, order: 6 })
|
||||
await addPhase(genericId, 'feedbackCallLead', 'Feedback Call (Lead)', 'lead', { order: 7 })
|
||||
|
||||
// ── AssignmentType 2: Case Interview ────────────────────────────────────
|
||||
const caseId = await db.assignmentTypes.add({
|
||||
name: 'Case Interview',
|
||||
defaultPhaseKeys: ['preBriefing', 'briefing', 'delivery', 'feedbackCallClient', 'feedbackCallLead'],
|
||||
})
|
||||
const caseCriteria = [kritStruktur, kritHypo, kritAnalyse, kritKlarheit, kritZeitmanage, kritUeberzeug, kritFragen]
|
||||
await addPhase(caseId, 'preBriefing', 'Pre-Briefing (Lead)', 'lead', { order: 1 })
|
||||
await addPhase(caseId, 'briefing', 'Case Briefing (Kunde)', 'client', { conv: true, assess: true, criteriaIds: caseCriteria, order: 2 })
|
||||
await addPhase(caseId, 'delivery', 'Case Delivery (Kunde)', 'client', { conv: true, assess: true, criteriaIds: caseCriteria, order: 3 })
|
||||
await addPhase(caseId, 'feedbackCallClient', 'Feedback Call (Kunde)', 'client', { conv: true, assess: true, criteriaIds: caseCriteria, order: 4 })
|
||||
await addPhase(caseId, 'feedbackCallLead', 'Feedback Call (Lead)', 'lead', { order: 5 })
|
||||
|
||||
// ── AssignmentType 3: Stakeholder Meeting / Pitch ────────────────────────
|
||||
const pitchId = await db.assignmentTypes.add({
|
||||
name: 'Stakeholder Meeting / Pitch',
|
||||
defaultPhaseKeys: ['preBriefing', 'briefing', 'alignmentCall', 'preDelivery', 'delivery', 'feedbackCallClient', 'feedbackCallLead'],
|
||||
})
|
||||
const pitchCriteria = [kritVorb, kritKlarheit, kritUeberzeug, kritStakeholder, kritFragen, kritEmpathie, kritInitiative]
|
||||
await addPhase(pitchId, 'preBriefing', 'Pre-Briefing (Lead)', 'lead', { order: 1 })
|
||||
await addPhase(pitchId, 'briefing', 'Briefing (Kunde)', 'client', { conv: true, assess: true, criteriaIds: pitchCriteria, order: 2 })
|
||||
await addPhase(pitchId, 'alignmentCall', 'Alignment Call (Kunde)', 'client', { repeatable: true, conv: true, assess: true, criteriaIds: pitchCriteria, order: 3 })
|
||||
await addPhase(pitchId, 'preDelivery', 'Pre-Delivery (Lead)', 'lead', { order: 4 })
|
||||
await addPhase(pitchId, 'delivery', 'Pitch / Delivery (Kunde)', 'client', { conv: true, assess: true, criteriaIds: pitchCriteria, order: 5 })
|
||||
await addPhase(pitchId, 'feedbackCallClient', 'Feedback Call (Kunde)', 'client', { conv: true, assess: true, criteriaIds: pitchCriteria, order: 6 })
|
||||
await addPhase(pitchId, 'feedbackCallLead', 'Feedback Call (Lead)', 'lead', { order: 7 })
|
||||
}
|
||||
65
src/db/seeds/feedbackStructure.ts
Normal file
65
src/db/seeds/feedbackStructure.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import { db } from '../schema'
|
||||
|
||||
export async function seedFeedbackStructureIfEmpty() {
|
||||
const count = await db.feedbackDimensions.count()
|
||||
if (count > 0) return
|
||||
|
||||
type CatSeed = { name: string; items: string[] }
|
||||
type DimSeed = { name: string; cats: CatSeed[] }
|
||||
|
||||
const dimensions: DimSeed[] = [
|
||||
{
|
||||
name: 'Mindset, Commitment, Collaboration',
|
||||
cats: [
|
||||
{ name: 'Persönliche Einstellung', items: ['Ruhig bei Stress', 'Offen für neue Themen', 'Auftreten / Ausstrahlung', 'Pro aktiv', 'Lösungsorientiert'] },
|
||||
{ name: 'Professionalität', items: ['Pünktlichkeit', 'Professionell bei informellen Gesprächen', 'Adressatengerecht', 'Hohes Maß an Selbststeuerung', 'Klarheit', 'Zuverlässigkeit'] },
|
||||
{ name: 'Commitment', items: ['Offen für spontane Änderungen', 'Selbständiger Lösungsansatz', 'Pro aktive Ideen', 'Verantwortungsbewusst', 'Hilfsbereit', 'Teamorientiert'] },
|
||||
{ name: 'Kommunikationsfähigkeit', items: ['To the point', 'Recap-Fähigkeit', 'Active Listening', 'Souveränität'] },
|
||||
{ name: 'Zusammenarbeit', items: ['Regelmäßige Abstimmung', 'Unterstützung bei Bedarf', 'Know-How Sharing', 'Starkes Teamverhalten', 'Verantwortung und Vertrauen', 'Inhaltlich unterstützend'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Running Our Projects',
|
||||
cats: [
|
||||
{ name: 'Ergebnisverantwortung', items: ['Fokussierung', 'Priorisierung (Impact Driven)', 'Machbarkeit / Umsetzbarkeit', 'Alternativvorschläge', 'Interne Abstimmung und Feedback'] },
|
||||
{ name: 'Arbeitsstil', items: ['Komplexitätsmanagement', 'Eigenverantwortung', 'Feedbackaffinität', 'Verantwortungsbewusst / vorausschauend', 'Selbstreflektiert, lösungsorientiert', 'Offen, initiativ, kollegial', 'Organisiert, kommunikationsfähig', 'Lernfähig, anpassungsbereit, flexibel'] },
|
||||
{ name: 'Kundenmanagement', items: ['Aktives Erwartungsmanagement', 'Selbstsicherheit und fachliche Überzeugung', 'Aktive Advisory', 'Realistisch', 'Kundenfokussiert'] },
|
||||
{ name: 'Lieferzeiten', items: ['Verlässlichkeit', 'Fortschrittskontrolle und Planadaption', 'Aktives Scoping', 'Meetingsvorbereitung', 'Transparent'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Growing our People',
|
||||
cats: [
|
||||
{ name: 'Networking', items: ['Cross-Collaboration', 'Network Growth', 'Network Utilization'] },
|
||||
{ name: 'Learning und Development', items: ['Kritikfähigkeit', 'Wissensaufbau (Kompetenzausstrahlung)', 'Umsetzungsbereitschaft Feedback'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Functional & Technical Skills',
|
||||
cats: [
|
||||
{ name: 'Qualität des Inhalts', items: ['Belastbarkeit', 'Nachvollziehbarkeit', 'Logische Storyline'] },
|
||||
{ name: 'Inhaltliche Vollständigkeit', items: ['Wesentliche Elemente vorhanden', 'Realitätsnähe', 'Nutzenfokus'] },
|
||||
{ name: 'Qualität der Unterlage', items: ['Design nach Standardvorgaben', 'Sprachliche Präzision', 'Visualisierungen unterstützen Kernbotschaften'] },
|
||||
{ name: 'Prozesstreue', items: ['Kenne und befolge Prozesse und Vorgaben', 'Nachvollziehbarkeit und Transparenz', 'Qualitätschecks'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Innovation und Asset Development',
|
||||
cats: [
|
||||
{ name: 'Innovation', items: ['Aktiver Vorschlag für Verbesserungen', 'Neue Ideen zur Verbesserung', 'Nutze aktuellen Wissensstand', 'Liefere über den Kundenerwartungen'] },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
for (let di = 0; di < dimensions.length; di++) {
|
||||
const dim = dimensions[di]
|
||||
const dimId = await db.feedbackDimensions.add({ name: dim.name, order: di + 1 })
|
||||
for (let ci = 0; ci < dim.cats.length; ci++) {
|
||||
const cat = dim.cats[ci]
|
||||
const catId = await db.feedbackCategories.add({ dimensionId: dimId, name: cat.name, order: ci + 1 })
|
||||
for (let ii = 0; ii < cat.items.length; ii++) {
|
||||
await db.feedbackCriterionItems.add({ categoryId: catId, name: cat.items[ii], order: ii + 1 })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
src/db/seeds/index.ts
Normal file
3
src/db/seeds/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export { seedIfEmpty } from './assignmentTypes'
|
||||
export { seedFeedbackStructureIfEmpty } from './feedbackStructure'
|
||||
export { seedCriterionMappingsIfEmpty, migrateToUnifiedCriteriaIfNeeded } from './migrations'
|
||||
106
src/db/seeds/migrations.ts
Normal file
106
src/db/seeds/migrations.ts
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import { db } from '../schema'
|
||||
|
||||
export async function seedCriterionMappingsIfEmpty() {
|
||||
const count = await db.criterionCategoryMappings.count()
|
||||
if (count > 0) return
|
||||
|
||||
const [crits, cats] = await Promise.all([
|
||||
db.criteria.toArray(),
|
||||
db.feedbackCategories.toArray(),
|
||||
])
|
||||
if (crits.length === 0 || cats.length === 0) return
|
||||
|
||||
const c = (name: string) => crits.find(x => x.name === name)?.id
|
||||
const f = (name: string) => cats.find(x => x.name === name)?.id
|
||||
|
||||
const mappings: [string, string][] = [
|
||||
['Klarheit & Struktur', 'Kommunikationsfähigkeit'],
|
||||
['Klarheit & Struktur', 'Qualität des Inhalts'],
|
||||
['Überzeugungskraft', 'Kommunikationsfähigkeit'],
|
||||
['Überzeugungskraft', 'Kundenmanagement'],
|
||||
['Aktives Zuhören', 'Kommunikationsfähigkeit'],
|
||||
['Umgang mit Fragen', 'Kommunikationsfähigkeit'],
|
||||
['Umgang mit Fragen', 'Kundenmanagement'],
|
||||
['Fachliche Tiefe', 'Qualität des Inhalts'],
|
||||
['Fachliche Tiefe', 'Inhaltliche Vollständigkeit'],
|
||||
['Problemlösung', 'Ergebnisverantwortung'],
|
||||
['Problemlösung', 'Arbeitsstil'],
|
||||
['Stakeholder-Verständnis', 'Kundenmanagement'],
|
||||
['Stakeholder-Verständnis', 'Persönliche Einstellung'],
|
||||
['Empathie', 'Persönliche Einstellung'],
|
||||
['Empathie', 'Kommunikationsfähigkeit'],
|
||||
['Vorbereitung', 'Professionalität'],
|
||||
['Vorbereitung', 'Lieferzeiten'],
|
||||
['Eigeninitiative', 'Commitment'],
|
||||
['Eigeninitiative', 'Persönliche Einstellung'],
|
||||
['Problemstrukturierung', 'Ergebnisverantwortung'],
|
||||
['Problemstrukturierung', 'Qualität des Inhalts'],
|
||||
['Hypothesenbildung', 'Ergebnisverantwortung'],
|
||||
['Hypothesenbildung', 'Arbeitsstil'],
|
||||
['Rechnen & Analyse', 'Qualität des Inhalts'],
|
||||
['Rechnen & Analyse', 'Inhaltliche Vollständigkeit'],
|
||||
['Zeitmanagement', 'Lieferzeiten'],
|
||||
['Zeitmanagement', 'Arbeitsstil'],
|
||||
]
|
||||
|
||||
for (const [critName, catName] of mappings) {
|
||||
const criterionId = c(critName)
|
||||
const feedbackCategoryId = f(catName)
|
||||
if (criterionId && feedbackCategoryId) {
|
||||
await db.criterionCategoryMappings.add({ criterionId, feedbackCategoryId })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Löscht nur Bewertungs-Scores (keine Textinhalte), verknüpft PhaseTemplates
|
||||
// mit FeedbackCategory-IDs statt veralteten Criterion-IDs.
|
||||
export async function migrateToUnifiedCriteriaIfNeeded() {
|
||||
const cats = await db.feedbackCategories.toArray()
|
||||
if (cats.length === 0) return
|
||||
|
||||
const templates = await db.phaseTemplates.toArray()
|
||||
if (templates.length === 0) return
|
||||
|
||||
const catIds = new Set(cats.map(c => c.id!))
|
||||
const firstWithIds = templates.find(t => t.criteriaIds.length > 0)
|
||||
if (firstWithIds && catIds.has(firstWithIds.criteriaIds[0])) return
|
||||
|
||||
const cat = (name: string) => cats.find(c => c.name === name)?.id
|
||||
|
||||
await db.conversationSkillScores.clear()
|
||||
await db.assessments.clear()
|
||||
|
||||
const genericCats = [
|
||||
cat('Kommunikationsfähigkeit'), cat('Persönliche Einstellung'),
|
||||
cat('Kundenmanagement'), cat('Ergebnisverantwortung'), cat('Qualität des Inhalts'),
|
||||
].filter(Boolean) as number[]
|
||||
|
||||
const caseCats = [
|
||||
cat('Kommunikationsfähigkeit'), cat('Ergebnisverantwortung'),
|
||||
cat('Arbeitsstil'), cat('Qualität des Inhalts'), cat('Inhaltliche Vollständigkeit'),
|
||||
].filter(Boolean) as number[]
|
||||
|
||||
const pitchCats = [
|
||||
cat('Kommunikationsfähigkeit'), cat('Persönliche Einstellung'),
|
||||
cat('Kundenmanagement'), cat('Ergebnisverantwortung'),
|
||||
cat('Qualität des Inhalts'), cat('Qualität der Unterlage'),
|
||||
].filter(Boolean) as number[]
|
||||
|
||||
const types = await db.assignmentTypes.toArray()
|
||||
for (const tmpl of templates) {
|
||||
if (!tmpl.hasAssessment && !tmpl.hasConversation) {
|
||||
await db.phaseTemplates.update(tmpl.id!, { criteriaIds: [] })
|
||||
continue
|
||||
}
|
||||
const aType = types.find(t => t.id === tmpl.assignmentTypeId)
|
||||
let newIds: number[]
|
||||
if (aType?.name === 'Case Interview') {
|
||||
newIds = caseCats
|
||||
} else if (aType?.name === 'Stakeholder Meeting / Pitch') {
|
||||
newIds = pitchCats
|
||||
} else {
|
||||
newIds = genericCats
|
||||
}
|
||||
await db.phaseTemplates.update(tmpl.id!, { criteriaIds: newIds })
|
||||
}
|
||||
}
|
||||
202
src/db/types.ts
Normal file
202
src/db/types.ts
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
// ─── Bewertungs-Skala ────────────────────────────────────────────────────────
|
||||
|
||||
export type FeedbackRating =
|
||||
| 'na'
|
||||
| 'not_client_ready'
|
||||
| 'partially_client_ready'
|
||||
| 'nearly_client_ready'
|
||||
| 'client_ready'
|
||||
|
||||
// ─── Legacy-Konfiguration (nur noch für Evaluation.tsx) ───────────────────────
|
||||
|
||||
export interface Category {
|
||||
id?: number
|
||||
name: string
|
||||
order: number
|
||||
}
|
||||
|
||||
export interface Criterion {
|
||||
id?: number
|
||||
categoryId: number
|
||||
name: string
|
||||
description: string
|
||||
weight: number
|
||||
order: number
|
||||
}
|
||||
|
||||
// ─── Assignment-Typen & Phase-Templates ──────────────────────────────────────
|
||||
|
||||
export type PhaseKey = string
|
||||
|
||||
export interface AssignmentType {
|
||||
id?: number
|
||||
name: string
|
||||
defaultPhaseKeys: PhaseKey[]
|
||||
criteriaIds?: number[] // FeedbackCriterionItem IDs; leer = alle anzeigen
|
||||
}
|
||||
|
||||
export interface PhaseTemplate {
|
||||
id?: number
|
||||
assignmentTypeId: number
|
||||
phaseKey: PhaseKey
|
||||
label: string
|
||||
withWhom: 'lead' | 'client'
|
||||
isRepeatable: boolean
|
||||
hasConversation: boolean
|
||||
hasAssessment: boolean
|
||||
criteriaIds: number[]
|
||||
order: number
|
||||
}
|
||||
|
||||
// ─── Stammdaten ───────────────────────────────────────────────────────────────
|
||||
|
||||
export interface Group {
|
||||
id?: number
|
||||
name: string
|
||||
cohortYear: number
|
||||
}
|
||||
|
||||
export interface Consultant {
|
||||
id?: number
|
||||
groupId: number
|
||||
firstName: string
|
||||
lastName: string
|
||||
}
|
||||
|
||||
// ─── Assignments ─────────────────────────────────────────────────────────────
|
||||
|
||||
export interface Assignment {
|
||||
id?: number
|
||||
title: string
|
||||
client: string
|
||||
assignmentTypeId: number
|
||||
instituteeIds: number[]
|
||||
leadName: string
|
||||
status: 'active' | 'done'
|
||||
createdAt: string
|
||||
assignmentNumber?: string
|
||||
description?: string
|
||||
instituteeEnrollments?: Array<{
|
||||
instituteeId: number
|
||||
from?: string
|
||||
to?: string
|
||||
}>
|
||||
}
|
||||
|
||||
// ─── Meeting-Instanzen ────────────────────────────────────────────────────────
|
||||
|
||||
export interface MeetingInstance {
|
||||
id?: number
|
||||
assignmentId: number
|
||||
phaseTemplateId: number
|
||||
phaseIndex: number
|
||||
date: string
|
||||
status: 'planned' | 'active' | 'done'
|
||||
generalNotes: string
|
||||
deletedAt?: string
|
||||
}
|
||||
|
||||
// ─── Gesprächs-Zeitleiste ─────────────────────────────────────────────────────
|
||||
|
||||
export interface ConversationEntry {
|
||||
id?: number
|
||||
meetingInstanceId: number
|
||||
instituteeId: number
|
||||
sequenceIndex: number
|
||||
note: string
|
||||
fillerCount: number
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
// criteriaId → FeedbackCategory.id (Schnellbewertung während Gesprächsbeitrag)
|
||||
export interface ConversationSkillScore {
|
||||
id?: number
|
||||
conversationEntryId: number
|
||||
criteriaId: number
|
||||
score: FeedbackRating | null
|
||||
}
|
||||
|
||||
// ─── Gesamtbewertung ─────────────────────────────────────────────────────────
|
||||
|
||||
// criteriaId → FeedbackCriterionItem.id (Gesamtbewertung am Meeting-Ende)
|
||||
export interface Assessment {
|
||||
id?: number
|
||||
meetingInstanceId: number
|
||||
instituteeId: number
|
||||
criteriaId: number
|
||||
score: FeedbackRating | null
|
||||
note: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
// ─── Feedback-Entwurf (Legacy) ────────────────────────────────────────────────
|
||||
|
||||
export interface FeedbackDraft {
|
||||
id?: number
|
||||
assignmentId: number
|
||||
instituteeId: number
|
||||
content: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
// ─── Strukturiertes Assignment-Feedback ───────────────────────────────────────
|
||||
|
||||
export interface FeedbackDimension {
|
||||
id?: number
|
||||
name: string
|
||||
order: number
|
||||
}
|
||||
|
||||
export interface FeedbackCategory {
|
||||
id?: number
|
||||
dimensionId: number
|
||||
name: string
|
||||
order: number
|
||||
}
|
||||
|
||||
export interface FeedbackCriterionItem {
|
||||
id?: number
|
||||
categoryId: number
|
||||
name: string
|
||||
weight?: number
|
||||
order: number
|
||||
}
|
||||
|
||||
export interface AssignmentFeedback {
|
||||
id?: number
|
||||
assignmentId: number
|
||||
instituteeId: number
|
||||
status: 'draft' | 'final'
|
||||
aiModel: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface FeedbackCategoryRating {
|
||||
id?: number
|
||||
assignmentFeedbackId: number
|
||||
feedbackCategoryId: number
|
||||
rating: FeedbackRating
|
||||
}
|
||||
|
||||
export interface FeedbackDimensionText {
|
||||
id?: number
|
||||
assignmentFeedbackId: number
|
||||
feedbackDimensionId: number
|
||||
achievements: string
|
||||
developmentNeeds: string
|
||||
}
|
||||
|
||||
// ─── Legacy-Mappings ──────────────────────────────────────────────────────────
|
||||
|
||||
export interface CriterionCategoryMapping {
|
||||
id?: number
|
||||
criterionId: number
|
||||
feedbackCategoryId: number
|
||||
}
|
||||
|
||||
export interface CriterionLevelDescription {
|
||||
id?: number
|
||||
criterionItemId: number
|
||||
rating: FeedbackRating
|
||||
text: string
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user