- Introduced `CategoryBreakdown`, `JiraWeeklyMetrics`, `PeriodSelector`, and `VelocityMetrics` components to enhance the weekly summary dashboard. - Updated `WeeklySummaryClient` to manage period selection and PDF export functionality. - Enhanced `WeeklySummaryService` to support period comparisons and activity categorization. - Added new API route for fetching weekly summary data based on selected period. - Updated `package.json` and `package-lock.json` to include `jspdf` and related types for PDF generation. - Marked several tasks as complete in `TODO.md` to reflect progress on summary features.
186 lines
5.5 KiB
TypeScript
186 lines
5.5 KiB
TypeScript
export interface PredefinedCategory {
|
|
name: string;
|
|
color: string;
|
|
keywords: string[];
|
|
icon: string;
|
|
}
|
|
|
|
export const PREDEFINED_CATEGORIES: PredefinedCategory[] = [
|
|
{
|
|
name: 'Dev',
|
|
color: '#3b82f6', // Blue
|
|
icon: '💻',
|
|
keywords: [
|
|
'code', 'coding', 'development', 'develop', 'dev', 'programming', 'program',
|
|
'bug', 'fix', 'debug', 'feature', 'implement', 'refactor', 'review',
|
|
'api', 'database', 'db', 'frontend', 'backend', 'ui', 'ux',
|
|
'component', 'service', 'function', 'method', 'class',
|
|
'git', 'commit', 'merge', 'pull request', 'pr', 'deploy', 'deployment',
|
|
'test', 'testing', 'unit test', 'integration'
|
|
]
|
|
},
|
|
{
|
|
name: 'Meeting',
|
|
color: '#8b5cf6', // Purple
|
|
icon: '🤝',
|
|
keywords: [
|
|
'meeting', 'réunion', 'call', 'standup', 'daily', 'retrospective', 'retro',
|
|
'planning', 'demo', 'presentation', 'sync', 'catch up', 'catchup',
|
|
'interview', 'discussion', 'brainstorm', 'workshop', 'session',
|
|
'one on one', '1on1', 'review meeting', 'sprint planning'
|
|
]
|
|
},
|
|
{
|
|
name: 'Admin',
|
|
color: '#6b7280', // Gray
|
|
icon: '📋',
|
|
keywords: [
|
|
'admin', 'administration', 'paperwork', 'documentation', 'doc', 'docs',
|
|
'report', 'reporting', 'timesheet', 'expense', 'invoice',
|
|
'email', 'mail', 'communication', 'update', 'status',
|
|
'config', 'configuration', 'setup', 'installation', 'maintenance',
|
|
'backup', 'security', 'permission', 'user management'
|
|
]
|
|
},
|
|
{
|
|
name: 'Learning',
|
|
color: '#10b981', // Green
|
|
icon: '📚',
|
|
keywords: [
|
|
'learning', 'learn', 'study', 'training', 'course', 'tutorial',
|
|
'research', 'reading', 'documentation', 'knowledge', 'skill',
|
|
'certification', 'workshop', 'seminar', 'conference',
|
|
'practice', 'exercise', 'experiment', 'exploration', 'investigate'
|
|
]
|
|
}
|
|
];
|
|
|
|
export class TaskCategorizationService {
|
|
/**
|
|
* Suggère une catégorie basée sur le titre et la description d'une tâche
|
|
*/
|
|
static suggestCategory(title: string, description?: string): PredefinedCategory | null {
|
|
const text = `${title} ${description || ''}`.toLowerCase();
|
|
|
|
// Compte les matches pour chaque catégorie
|
|
const categoryScores = PREDEFINED_CATEGORIES.map(category => {
|
|
const matches = category.keywords.filter(keyword =>
|
|
text.includes(keyword.toLowerCase())
|
|
).length;
|
|
|
|
return {
|
|
category,
|
|
score: matches
|
|
};
|
|
});
|
|
|
|
// Trouve la meilleure catégorie
|
|
const bestMatch = categoryScores.reduce((best, current) =>
|
|
current.score > best.score ? current : best
|
|
);
|
|
|
|
// Retourne la catégorie seulement s'il y a au moins un match
|
|
return bestMatch.score > 0 ? bestMatch.category : null;
|
|
}
|
|
|
|
/**
|
|
* Suggère plusieurs catégories avec leur score de confiance
|
|
*/
|
|
static suggestCategoriesWithScore(title: string, description?: string): Array<{
|
|
category: PredefinedCategory;
|
|
score: number;
|
|
confidence: number;
|
|
}> {
|
|
const text = `${title} ${description || ''}`.toLowerCase();
|
|
|
|
const categoryScores = PREDEFINED_CATEGORIES.map(category => {
|
|
const matches = category.keywords.filter(keyword =>
|
|
text.includes(keyword.toLowerCase())
|
|
);
|
|
|
|
const score = matches.length;
|
|
const confidence = Math.min((score / 3) * 100, 100); // Max 100% de confiance avec 3+ mots-clés
|
|
|
|
return {
|
|
category,
|
|
score,
|
|
confidence
|
|
};
|
|
});
|
|
|
|
return categoryScores
|
|
.filter(item => item.score > 0)
|
|
.sort((a, b) => b.score - a.score);
|
|
}
|
|
|
|
/**
|
|
* Analyse les activités et retourne la répartition par catégorie
|
|
*/
|
|
static analyzeActivitiesByCategory(activities: Array<{ title: string; description?: string }>): {
|
|
[categoryName: string]: {
|
|
count: number;
|
|
percentage: number;
|
|
color: string;
|
|
icon: string;
|
|
}
|
|
} {
|
|
const categoryCounts: { [key: string]: number } = {};
|
|
const uncategorized = { count: 0 };
|
|
|
|
// Initialiser les compteurs
|
|
PREDEFINED_CATEGORIES.forEach(cat => {
|
|
categoryCounts[cat.name] = 0;
|
|
});
|
|
|
|
// Analyser chaque activité
|
|
activities.forEach(activity => {
|
|
const suggestedCategory = this.suggestCategory(activity.title, activity.description);
|
|
|
|
if (suggestedCategory) {
|
|
categoryCounts[suggestedCategory.name]++;
|
|
} else {
|
|
uncategorized.count++;
|
|
}
|
|
});
|
|
|
|
const total = activities.length;
|
|
const result: { [categoryName: string]: { count: number; percentage: number; color: string; icon: string } } = {};
|
|
|
|
// Ajouter les catégories prédéfinies
|
|
PREDEFINED_CATEGORIES.forEach(category => {
|
|
const count = categoryCounts[category.name];
|
|
result[category.name] = {
|
|
count,
|
|
percentage: total > 0 ? (count / total) * 100 : 0,
|
|
color: category.color,
|
|
icon: category.icon
|
|
};
|
|
});
|
|
|
|
// Ajouter "Autre" si nécessaire
|
|
if (uncategorized.count > 0) {
|
|
result['Autre'] = {
|
|
count: uncategorized.count,
|
|
percentage: total > 0 ? (uncategorized.count / total) * 100 : 0,
|
|
color: '#d1d5db',
|
|
icon: '❓'
|
|
};
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Retourne les tags suggérés pour une tâche
|
|
*/
|
|
static getSuggestedTags(title: string, description?: string): string[] {
|
|
const suggestions = this.suggestCategoriesWithScore(title, description);
|
|
|
|
return suggestions
|
|
.filter(s => s.confidence >= 30) // Seulement les suggestions avec 30%+ de confiance
|
|
.slice(0, 2) // Maximum 2 suggestions
|
|
.map(s => s.category.name);
|
|
}
|
|
}
|
|
|