INSIGHT-MVP/packages/frontend/src/profile/sections/SkillsSection.tsx
Thomas Reitz a275cf83e1 feat: move input forms into section headers for Skills, Languages and Experience
Input fields now appear inline next to the section title, matching the
layout pattern used by Projects, Certifications and Attachments sections.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 10:47:31 +01:00

107 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, type KeyboardEvent } from 'react';
import api from '../../api/client';
import styles from '../ExpertProfileTab.module.css';
interface SkillsSectionProps {
skills: string[];
onUpdate: () => Promise<void>;
}
export function SkillsSection({ skills, onUpdate }: SkillsSectionProps) {
const [newSkill, setNewSkill] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const handleAdd = async () => {
const skill = newSkill.trim();
if (!skill) return;
if (skills.includes(skill)) {
setError('Skill bereits vorhanden');
return;
}
setLoading(true);
setError('');
try {
await api.patch('/expert-profile/me/skills', { skills: [...skills, skill] });
setNewSkill('');
await onUpdate();
} catch {
setError('Fehler beim Hinzufügen');
} finally {
setLoading(false);
}
};
const handleRemove = async (skillToRemove: string) => {
setLoading(true);
setError('');
try {
await api.patch('/expert-profile/me/skills', {
skills: skills.filter((s) => s !== skillToRemove),
});
await onUpdate();
} catch {
setError('Fehler beim Entfernen');
} finally {
setLoading(false);
}
};
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
e.preventDefault();
handleAdd();
}
};
return (
<div className={styles.section}>
<div className={styles.sectionHeader}>
<h3 className={styles.sectionTitle}>Skills</h3>
<div className={styles.chipInput}>
<input
type="text"
value={newSkill}
onChange={(e) => setNewSkill(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Neuer Skill..."
maxLength={100}
disabled={loading}
/>
<button
type="button"
className={styles.btnPrimary}
onClick={handleAdd}
disabled={loading || !newSkill.trim()}
>
Hinzufügen
</button>
</div>
</div>
{error && <div className={styles.error}>{error}</div>}
{skills.length > 0 ? (
<div className={styles.chipContainer}>
{skills.map((skill) => (
<span key={skill} className={styles.chip}>
{skill}
<button
type="button"
className={styles.chipRemove}
onClick={() => handleRemove(skill)}
disabled={loading}
aria-label={`${skill} entfernen`}
>
×
</button>
</span>
))}
</div>
) : (
<p className={styles.emptyState}>Noch keine Skills hinzugefügt</p>
)}
</div>
);
}