feat: importance in db and mentorcard home colors
This commit is contained in:
37
app/api/admin/skills/[skillId]/importance/route.ts
Normal file
37
app/api/admin/skills/[skillId]/importance/route.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { SkillsService } from "@/services/skills-service";
|
||||
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { skillId: string } }
|
||||
) {
|
||||
try {
|
||||
const { importance } = await request.json();
|
||||
const { skillId } = params;
|
||||
|
||||
// Validation
|
||||
if (
|
||||
!importance ||
|
||||
!["incontournable", "majeure", "standard"].includes(importance)
|
||||
) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error:
|
||||
"Importance invalide. Doit être 'incontournable', 'majeure' ou 'standard'",
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Mettre à jour l'importance
|
||||
await SkillsService.updateSkillImportance(skillId, importance);
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error("Error updating skill importance:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Erreur lors de la mise à jour de l'importance" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ export interface Skill {
|
||||
categoryId: string;
|
||||
category: string;
|
||||
usageCount: number;
|
||||
importance: string;
|
||||
}
|
||||
|
||||
export interface Team {
|
||||
@@ -59,6 +60,13 @@ export class AdminClient extends BaseHttpClient {
|
||||
await this.delete(`/admin/skills/categories/${categoryId}`);
|
||||
}
|
||||
|
||||
async updateSkillImportance(
|
||||
skillId: string,
|
||||
importance: "incontournable" | "majeure" | "standard"
|
||||
): Promise<void> {
|
||||
await this.put(`/admin/skills/${skillId}/importance`, { importance });
|
||||
}
|
||||
|
||||
// Teams Management
|
||||
async getTeams(): Promise<Team[]> {
|
||||
return await this.get<Team[]>(`/admin/teams`);
|
||||
|
||||
89
components/admin/skills/importance-badge.tsx
Normal file
89
components/admin/skills/importance-badge.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { ChevronDown, Check } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { getImportanceColors } from "@/lib/tech-colors";
|
||||
|
||||
interface ImportanceBadgeProps {
|
||||
importance: string;
|
||||
onImportanceChange: (newImportance: string) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function ImportanceBadge({
|
||||
importance,
|
||||
onImportanceChange,
|
||||
disabled = false,
|
||||
}: ImportanceBadgeProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const colors = getImportanceColors(importance);
|
||||
|
||||
const handleImportanceChange = (newImportance: string) => {
|
||||
onImportanceChange(newImportance);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const IMPORTANCE_LABELS = {
|
||||
incontournable: "Incontournable",
|
||||
majeure: "Majeure",
|
||||
standard: "Standard",
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className={`h-6 px-2 text-xs font-medium border ${colors.bg} ${
|
||||
colors.text
|
||||
} ${colors.border} hover:opacity-80 transition-all ${
|
||||
disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"
|
||||
}`}
|
||||
disabled={disabled}
|
||||
>
|
||||
<span
|
||||
className={`w-2 h-2 rounded-full ${colors.text.replace(
|
||||
"text-",
|
||||
"bg-"
|
||||
)}`}
|
||||
/>
|
||||
{IMPORTANCE_LABELS[importance as keyof typeof IMPORTANCE_LABELS] ||
|
||||
importance}
|
||||
{!disabled && <ChevronDown className="w-3 h-3 ml-1" />}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
{!disabled && (
|
||||
<DropdownMenuContent align="end" className="w-40">
|
||||
{Object.entries(IMPORTANCE_LABELS).map(([key, label]) => {
|
||||
const itemColors = getImportanceColors(key);
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={key}
|
||||
onClick={() => handleImportanceChange(key)}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<span
|
||||
className={`w-2 h-2 rounded-full ${itemColors.text.replace(
|
||||
"text-",
|
||||
"bg-"
|
||||
)}`}
|
||||
/>
|
||||
{label}
|
||||
{importance === key && <Check className="w-3 h-3 ml-auto" />}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import { TreeCategoryHeader, TreeItemRow } from "@/components/admin";
|
||||
import { TechIcon } from "@/components/icons/tech-icon";
|
||||
import { Skill } from "@/clients/domains/admin-client";
|
||||
import { ImportanceBadge } from "./importance-badge";
|
||||
|
||||
interface SkillsListProps {
|
||||
filteredSkillsByCategory: Record<string, Skill[]>;
|
||||
@@ -20,6 +21,7 @@ interface SkillsListProps {
|
||||
onEditSkill: (skill: Skill) => void;
|
||||
onDeleteSkill: (skillId: string) => void;
|
||||
onDeleteCategory?: (categoryName: string) => void; // Nouvelle prop
|
||||
onImportanceChange?: (skillId: string, newImportance: string) => void;
|
||||
}
|
||||
|
||||
export function SkillsList({
|
||||
@@ -29,6 +31,7 @@ export function SkillsList({
|
||||
onEditSkill,
|
||||
onDeleteSkill,
|
||||
onDeleteCategory,
|
||||
onImportanceChange,
|
||||
}: SkillsListProps) {
|
||||
// Fonction pour obtenir l'icône de la catégorie
|
||||
const getCategoryIcon = (category: string) => {
|
||||
@@ -108,6 +111,14 @@ export function SkillsList({
|
||||
onDelete={() => onDeleteSkill(skill.id)}
|
||||
canDelete={skill.usageCount === 0}
|
||||
showSeparator={skillIndex > 0}
|
||||
additionalInfo={
|
||||
<ImportanceBadge
|
||||
importance={skill.importance || "standard"}
|
||||
onImportanceChange={(newImportance: string) =>
|
||||
onImportanceChange?.(skill.id, newImportance)
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -51,6 +51,7 @@ export function SkillsManagementPage({
|
||||
handleEditSkill,
|
||||
handleUpdateSkill,
|
||||
handleDeleteSkill,
|
||||
handleImportanceChange,
|
||||
} = useSkillsManagement(skillCategories, initialSkills);
|
||||
|
||||
// Utilisation du hook factorisé pour la vue arborescente
|
||||
@@ -93,6 +94,13 @@ export function SkillsManagementPage({
|
||||
openEditDialog();
|
||||
};
|
||||
|
||||
const handleImportanceChangeWrapper = async (
|
||||
skillId: string,
|
||||
newImportance: string
|
||||
) => {
|
||||
await handleImportanceChange(skillId, newImportance);
|
||||
};
|
||||
|
||||
const headerActions = (
|
||||
<Button onClick={handleOpenCreateDialog}>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
@@ -137,6 +145,7 @@ export function SkillsManagementPage({
|
||||
onEditSkill={handleOpenEditDialog}
|
||||
onDeleteSkill={handleDeleteSkill}
|
||||
onDeleteCategory={handleDeleteCategory}
|
||||
onImportanceChange={handleImportanceChangeWrapper}
|
||||
/>
|
||||
</TreeViewPage>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TechIcon } from "@/components/icons/tech-icon";
|
||||
import { getTechColors } from "@/lib/tech-colors";
|
||||
import { getImportanceColors } from "@/lib/tech-colors";
|
||||
import { UserEvaluation, SkillCategory } from "@/lib/types";
|
||||
|
||||
interface MentorSectionProps {
|
||||
@@ -13,26 +13,6 @@ export function MentorSection({
|
||||
userEvaluation,
|
||||
skillCategories,
|
||||
}: MentorSectionProps) {
|
||||
// Récupérer les compétences où l'utilisateur peut être mentor
|
||||
const mentorSkills = userEvaluation.evaluations.flatMap((cat) => {
|
||||
const skillCategory = skillCategories.find(
|
||||
(sc) => sc.category === cat.category
|
||||
);
|
||||
return cat.skills
|
||||
.filter((skill) => skill.canMentor)
|
||||
.map((skill) => {
|
||||
const skillInfo = skillCategory?.skills.find(
|
||||
(s) => s.id === skill.skillId
|
||||
);
|
||||
return {
|
||||
id: skill.skillId,
|
||||
name: skillInfo?.name || skill.skillId,
|
||||
icon: skillInfo?.icon || "fas-code",
|
||||
level: skill.level,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// Récupérer les compétences maîtrisées (expert uniquement)
|
||||
const masteredSkills = userEvaluation.evaluations.flatMap((cat) => {
|
||||
const skillCategory = skillCategories.find(
|
||||
@@ -49,6 +29,28 @@ export function MentorSection({
|
||||
name: skillInfo?.name || skill.skillId,
|
||||
icon: skillInfo?.icon || "fas-code",
|
||||
level: skill.level,
|
||||
importance: skillInfo?.importance || "standard", // Récupérer l'importance depuis la base
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// Récupérer les compétences où l'utilisateur peut être mentor
|
||||
const mentorSkills = userEvaluation.evaluations.flatMap((cat) => {
|
||||
const skillCategory = skillCategories.find(
|
||||
(sc) => sc.category === cat.category
|
||||
);
|
||||
return cat.skills
|
||||
.filter((skill) => skill.canMentor)
|
||||
.map((skill) => {
|
||||
const skillInfo = skillCategory?.skills.find(
|
||||
(s) => s.id === skill.skillId
|
||||
);
|
||||
return {
|
||||
id: skill.skillId,
|
||||
name: skillInfo?.name || skill.skillId,
|
||||
icon: skillInfo?.icon || "fas-code",
|
||||
level: skill.level,
|
||||
importance: skillInfo?.importance || "standard", // Récupérer l'importance depuis la base
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -69,26 +71,11 @@ export function MentorSection({
|
||||
name: skillInfo?.name || skill.skillId,
|
||||
icon: skillInfo?.icon || "fas-code",
|
||||
level: skill.level,
|
||||
importance: skillInfo?.importance || "standard", // Récupérer l'importance depuis la base
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// Fonction pour déterminer la couleur d'une technologie
|
||||
const getTechColor = (techName: string) => {
|
||||
const lowerName = techName.toLowerCase();
|
||||
if (lowerName.includes("react") || lowerName.includes("next"))
|
||||
return "react";
|
||||
if (lowerName.includes("typescript") || lowerName.includes("javascript"))
|
||||
return "typescript";
|
||||
if (lowerName.includes("node")) return "nodejs";
|
||||
if (lowerName.includes("python")) return "python";
|
||||
if (lowerName.includes("docker")) return "docker";
|
||||
if (lowerName.includes("aws")) return "aws";
|
||||
if (lowerName.includes("kubernetes")) return "kubernetes";
|
||||
if (lowerName.includes("git")) return "default";
|
||||
return "default";
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`bg-white/5 border border-white/10 rounded-xl p-6 backdrop-blur-sm ${className}`}
|
||||
@@ -103,7 +90,7 @@ export function MentorSection({
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{masteredSkills.length > 0 ? (
|
||||
masteredSkills.map((tech) => {
|
||||
const colors = getTechColors(getTechColor(tech.name));
|
||||
const colors = getImportanceColors(tech.importance);
|
||||
return (
|
||||
<div
|
||||
key={tech.id}
|
||||
@@ -138,7 +125,7 @@ export function MentorSection({
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{mentorSkills.length > 0 ? (
|
||||
mentorSkills.map((tech) => {
|
||||
const colors = getTechColors(getTechColor(tech.name));
|
||||
const colors = getImportanceColors(tech.importance);
|
||||
return (
|
||||
<div
|
||||
key={tech.id}
|
||||
@@ -172,7 +159,7 @@ export function MentorSection({
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{learningSkills.length > 0 ? (
|
||||
learningSkills.map((tech) => {
|
||||
const colors = getTechColors(getTechColor(tech.name));
|
||||
const colors = getImportanceColors(tech.importance);
|
||||
return (
|
||||
<div
|
||||
key={tech.id}
|
||||
|
||||
@@ -96,10 +96,12 @@ export function useSkillsManagement(
|
||||
...skillFormData,
|
||||
category: category.category,
|
||||
categoryId: category.id,
|
||||
importance: "standard", // Valeur par défaut
|
||||
};
|
||||
|
||||
const newSkill = await adminClient.createSkill(skillData);
|
||||
setSkills([...skills, newSkill]);
|
||||
|
||||
setSkills((prevSkills) => [...prevSkills, newSkill]);
|
||||
resetForm();
|
||||
|
||||
toast({
|
||||
@@ -152,6 +154,7 @@ export function useSkillsManagement(
|
||||
...skillFormData,
|
||||
category: category.category,
|
||||
usageCount: editingSkill.usageCount,
|
||||
importance: editingSkill.importance || "standard", // Garder l'importance existante
|
||||
};
|
||||
|
||||
const updatedSkill = await adminClient.updateSkill(skillData);
|
||||
@@ -207,6 +210,36 @@ export function useSkillsManagement(
|
||||
}
|
||||
};
|
||||
|
||||
const handleImportanceChange = async (
|
||||
skillId: string,
|
||||
newImportance: string
|
||||
) => {
|
||||
try {
|
||||
await adminClient.updateSkillImportance(skillId, newImportance as any);
|
||||
|
||||
// Mettre à jour l'état local
|
||||
setSkills((prevSkills) =>
|
||||
prevSkills.map((skill) =>
|
||||
skill.id === skillId ? { ...skill, importance: newImportance } : skill
|
||||
)
|
||||
);
|
||||
|
||||
toast({
|
||||
title: "Succès",
|
||||
description: "Importance mise à jour avec succès",
|
||||
});
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
title: "Erreur",
|
||||
description:
|
||||
error.message || "Erreur lors de la mise à jour de l'importance",
|
||||
variant: "destructive",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
skills,
|
||||
isLoading,
|
||||
@@ -219,5 +252,6 @@ export function useSkillsManagement(
|
||||
handleEditSkill,
|
||||
handleUpdateSkill,
|
||||
handleDeleteSkill,
|
||||
handleImportanceChange,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,61 +1,36 @@
|
||||
// Couleurs officielle des technologies
|
||||
export const TECH_COLORS: Record<
|
||||
// Couleurs basées sur l'importance des skills
|
||||
export const IMPORTANCE_COLORS: Record<
|
||||
string,
|
||||
{ bg: string; text: string; border: string }
|
||||
> = {
|
||||
react: {
|
||||
bg: "bg-blue-500/10",
|
||||
text: "text-blue-600 dark:text-blue-400",
|
||||
border: "border-blue-500/20",
|
||||
incontournable: {
|
||||
bg: "bg-red-500/20",
|
||||
text: "text-red-400 dark:text-red-300",
|
||||
border: "border-red-500/30",
|
||||
},
|
||||
vue: {
|
||||
bg: "bg-green-500/10",
|
||||
text: "text-green-600 dark:text-green-400",
|
||||
border: "border-green-500/20",
|
||||
majeure: {
|
||||
bg: "bg-blue-500/20",
|
||||
text: "text-blue-400 dark:text-blue-300",
|
||||
border: "border-blue-500/30",
|
||||
},
|
||||
typescript: {
|
||||
bg: "bg-blue-500/10",
|
||||
text: "text-blue-600 dark:text-blue-400",
|
||||
border: "border-blue-500/20",
|
||||
},
|
||||
nextjs: {
|
||||
bg: "bg-gray-500/10",
|
||||
text: "text-gray-900 dark:text-gray-100",
|
||||
border: "border-gray-500/20",
|
||||
},
|
||||
nodejs: {
|
||||
bg: "bg-green-500/10",
|
||||
text: "text-green-600 dark:text-green-400",
|
||||
border: "border-green-500/20",
|
||||
},
|
||||
python: {
|
||||
bg: "bg-yellow-500/10",
|
||||
text: "text-yellow-600 dark:text-yellow-400",
|
||||
border: "border-yellow-500/20",
|
||||
},
|
||||
docker: {
|
||||
bg: "bg-blue-500/10",
|
||||
text: "text-blue-600 dark:text-blue-400",
|
||||
border: "border-blue-500/20",
|
||||
},
|
||||
kubernetes: {
|
||||
bg: "bg-indigo-500/10",
|
||||
text: "text-indigo-600 dark:text-indigo-400",
|
||||
border: "border-indigo-500/20",
|
||||
},
|
||||
aws: {
|
||||
bg: "bg-orange-500/10",
|
||||
text: "text-orange-600 dark:text-orange-400",
|
||||
border: "border-orange-500/20",
|
||||
},
|
||||
// Couleur par défaut
|
||||
default: {
|
||||
bg: "bg-muted",
|
||||
text: "text-foreground",
|
||||
border: "border-muted",
|
||||
standard: {
|
||||
bg: "bg-slate-500/20",
|
||||
text: "text-slate-400 dark:text-slate-300",
|
||||
border: "border-slate-500/30",
|
||||
},
|
||||
};
|
||||
|
||||
export function getTechColors(techId: string) {
|
||||
return TECH_COLORS[techId] || TECH_COLORS.default;
|
||||
// Fonction principale pour récupérer les couleurs basées sur l'importance
|
||||
export function getImportanceColors(importance: string) {
|
||||
return IMPORTANCE_COLORS[importance] || IMPORTANCE_COLORS.standard;
|
||||
}
|
||||
|
||||
// Fonction legacy pour la compatibilité (utilise seulement l'importance)
|
||||
export function getTechColors(importance: string) {
|
||||
return getImportanceColors(importance);
|
||||
}
|
||||
|
||||
// Fonction pour obtenir les couleurs d'importance uniquement
|
||||
export function getSmartColors(importance: string) {
|
||||
return getImportanceColors(importance);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ export interface Skill {
|
||||
description: string;
|
||||
icon?: string;
|
||||
links: string[];
|
||||
importance?: string;
|
||||
}
|
||||
|
||||
export interface SkillCategory {
|
||||
|
||||
@@ -29,6 +29,7 @@ CREATE TABLE skills (
|
||||
description TEXT,
|
||||
icon VARCHAR(100),
|
||||
category_id VARCHAR(50) REFERENCES skill_categories(id),
|
||||
importance VARCHAR(20) DEFAULT 'standard' CHECK (importance IN ('incontournable', 'majeure', 'standard')),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
@@ -88,6 +89,7 @@ CREATE TABLE skill_evaluations (
|
||||
-- Indexes for performance
|
||||
CREATE INDEX idx_teams_direction ON teams(direction);
|
||||
CREATE INDEX idx_skills_category_id ON skills(category_id);
|
||||
CREATE INDEX idx_skills_importance ON skills(importance);
|
||||
CREATE INDEX idx_skill_links_skill_id ON skill_links(skill_id);
|
||||
CREATE INDEX idx_users_team_id ON users(team_id);
|
||||
CREATE INDEX idx_users_email ON users(email);
|
||||
|
||||
72
scripts/migrations/add_skill_importance.sql
Normal file
72
scripts/migrations/add_skill_importance.sql
Normal file
@@ -0,0 +1,72 @@
|
||||
-- Migration pour ajouter la colonne importance aux skills
|
||||
-- Compatible avec les bases existantes
|
||||
|
||||
-- Étape 1: Vérifier si la colonne existe déjà
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'skills' AND column_name = 'importance'
|
||||
) THEN
|
||||
-- Ajouter la colonne importance
|
||||
ALTER TABLE skills
|
||||
ADD COLUMN importance VARCHAR(20) DEFAULT 'standard';
|
||||
|
||||
-- Ajouter la contrainte CHECK
|
||||
ALTER TABLE skills
|
||||
ADD CONSTRAINT skills_importance_check
|
||||
CHECK (importance IN ('incontournable', 'majeure', 'standard'));
|
||||
|
||||
RAISE NOTICE 'Colonne importance ajoutée avec succès';
|
||||
ELSE
|
||||
RAISE NOTICE 'Colonne importance existe déjà';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Étape 2: Mettre à jour les technologies incontournables
|
||||
UPDATE skills
|
||||
SET importance = 'incontournable'
|
||||
WHERE name IN (
|
||||
'React', 'TypeScript', 'JavaScript', 'Git', 'HTML', 'CSS',
|
||||
'Node.js', 'Python', 'SQL', 'HTTP', 'REST API', 'JSON',
|
||||
'GitHub', 'npm', 'yarn', 'package.json'
|
||||
);
|
||||
|
||||
-- Étape 3: Mettre à jour les technologies majeures
|
||||
UPDATE skills
|
||||
SET importance = 'majeure'
|
||||
WHERE name IN (
|
||||
'Next.js', 'Vue.js', 'Angular', 'Docker', 'Kubernetes',
|
||||
'AWS', 'Azure', 'Google Cloud', 'MongoDB', 'PostgreSQL',
|
||||
'Redis', 'GraphQL', 'Webpack', 'Vite', 'Jest', 'Cypress',
|
||||
'Tailwind CSS', 'Sass', 'Less', 'Redux', 'Zustand',
|
||||
'Prisma', 'TypeORM', 'Sequelize', 'Express.js', 'Fastify'
|
||||
);
|
||||
|
||||
-- Étape 4: Créer l'index s'il n'existe pas
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_indexes
|
||||
WHERE tablename = 'skills' AND indexname = 'idx_skills_importance'
|
||||
) THEN
|
||||
CREATE INDEX idx_skills_importance ON skills(importance);
|
||||
RAISE NOTICE 'Index idx_skills_importance créé avec succès';
|
||||
ELSE
|
||||
RAISE NOTICE 'Index idx_skills_importance existe déjà';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Étape 5: Vérification finale
|
||||
SELECT
|
||||
importance,
|
||||
COUNT(*) as count,
|
||||
STRING_AGG(name, ', ' ORDER BY name) as examples
|
||||
FROM skills
|
||||
GROUP BY importance
|
||||
ORDER BY
|
||||
CASE importance
|
||||
WHEN 'incontournable' THEN 1
|
||||
WHEN 'majeure' THEN 2
|
||||
WHEN 'standard' THEN 3
|
||||
END;
|
||||
39
scripts/populate_skill_importance.sql
Normal file
39
scripts/populate_skill_importance.sql
Normal file
@@ -0,0 +1,39 @@
|
||||
-- Script pour peupler la colonne importance des skills
|
||||
-- À exécuter après avoir ajouté la colonne importance
|
||||
|
||||
-- Technologies incontournables (fondamentales)
|
||||
UPDATE skills
|
||||
SET importance = 'incontournable'
|
||||
WHERE name IN (
|
||||
'React', 'TypeScript', 'JavaScript', 'Git', 'HTML', 'CSS',
|
||||
'Node.js', 'Python', 'SQL', 'HTTP', 'REST API', 'JSON',
|
||||
'GitHub', 'npm', 'yarn', 'package.json'
|
||||
);
|
||||
|
||||
-- Technologies majeures (importantes mais pas fondamentales)
|
||||
UPDATE skills
|
||||
SET importance = 'majeure'
|
||||
WHERE name IN (
|
||||
'Next.js', 'Vue.js', 'Angular', 'Docker', 'Kubernetes',
|
||||
'AWS', 'Azure', 'Google Cloud', 'MongoDB', 'PostgreSQL',
|
||||
'Redis', 'GraphQL', 'Webpack', 'Vite', 'Jest', 'Cypress',
|
||||
'Tailwind CSS', 'Sass', 'Less', 'Redux', 'Zustand',
|
||||
'Prisma', 'TypeORM', 'Sequelize', 'Express.js', 'Fastify'
|
||||
);
|
||||
|
||||
-- Le reste reste en 'standard' (valeur par défaut)
|
||||
-- Ces technologies sont importantes dans leur domaine mais pas critiques globalement
|
||||
|
||||
-- Vérification des mises à jour
|
||||
SELECT
|
||||
importance,
|
||||
COUNT(*) as count,
|
||||
STRING_AGG(name, ', ' ORDER BY name) as examples
|
||||
FROM skills
|
||||
GROUP BY importance
|
||||
ORDER BY
|
||||
CASE importance
|
||||
WHEN 'incontournable' THEN 1
|
||||
WHEN 'majeure' THEN 2
|
||||
WHEN 'standard' THEN 3
|
||||
END;
|
||||
@@ -20,6 +20,7 @@ export class SkillsService {
|
||||
'name', s.name,
|
||||
'description', s.description,
|
||||
'icon', s.icon,
|
||||
'importance', s.importance,
|
||||
'links', COALESCE(
|
||||
(SELECT json_agg(sl.url ORDER BY sl.id)
|
||||
FROM skill_links sl
|
||||
@@ -249,6 +250,7 @@ export class SkillsService {
|
||||
categoryId: string;
|
||||
category: string;
|
||||
usageCount: number;
|
||||
importance: string;
|
||||
}>
|
||||
> {
|
||||
const pool = getPool();
|
||||
@@ -260,11 +262,12 @@ export class SkillsService {
|
||||
s.icon,
|
||||
sc.id as category_id,
|
||||
sc.name as category_name,
|
||||
s.importance,
|
||||
COUNT(DISTINCT se.id) as usage_count
|
||||
FROM skills s
|
||||
LEFT JOIN skill_categories sc ON s.category_id = sc.id
|
||||
LEFT JOIN skill_evaluations se ON s.id = se.skill_id AND se.is_selected = true
|
||||
GROUP BY s.id, s.name, s.description, s.icon, sc.id, sc.name
|
||||
GROUP BY s.id, s.name, s.description, s.icon, sc.id, sc.name, s.importance
|
||||
ORDER BY s.name
|
||||
`;
|
||||
|
||||
@@ -279,6 +282,7 @@ export class SkillsService {
|
||||
categoryId: row.category_id,
|
||||
category: row.category_name,
|
||||
usageCount: parseInt(row.usage_count) || 0,
|
||||
importance: row.importance || "standard",
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error("Error fetching skills with usage:", error);
|
||||
@@ -286,6 +290,32 @@ export class SkillsService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update skill importance
|
||||
*/
|
||||
static async updateSkillImportance(
|
||||
skillId: string,
|
||||
importance: "incontournable" | "majeure" | "standard"
|
||||
): Promise<void> {
|
||||
const pool = getPool();
|
||||
const query = `
|
||||
UPDATE skills
|
||||
SET importance = $1, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $2
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = await pool.query(query, [importance, skillId]);
|
||||
|
||||
if (result.rowCount === 0) {
|
||||
throw new Error("Skill not found");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating skill importance:", error);
|
||||
throw new Error("Failed to update skill importance");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new skill for admin
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user