diff --git a/frontend/src/components/EmojiIconPicker.jsx b/frontend/src/components/EmojiIconPicker.jsx
new file mode 100644
index 0000000..8de5068
--- /dev/null
+++ b/frontend/src/components/EmojiIconPicker.jsx
@@ -0,0 +1,206 @@
+import { useState, useId } from 'react'
+import { ChevronDown, ChevronUp } from 'lucide-react'
+
+/**
+ * Kuratierte Emoji-Gruppen für Sport, Körper, Ernährung usw.
+ * Kann bei Bedarf erweitert werden (z. B. prop `extraGroups`).
+ */
+export const EMOJI_ICON_GROUPS = [
+ {
+ label: 'Training & Sport',
+ emojis: [
+ '🏃', '🚴', '🏊', '🧘', '🏋️', '⛷️', '🤸', '🥊', '🎾', '⚽', '🏀', '🤺',
+ '🚶', '🧗', '⛰️', '🏄', '🤿', '🥋', '🤼', '⛹️', '🤾', '🏌️', '🎿', '🧗♂️'
+ ]
+ },
+ {
+ label: 'Körper & Gesundheit',
+ emojis: [
+ '💪', '❤️', '🫀', '🦵', '🧠', '👁️', '⚖️', '📏', '🩺', '💊', '🌡️', '🫁'
+ ]
+ },
+ {
+ label: 'Ernährung',
+ emojis: [
+ '🍎', '🥩', '🥗', '🍽️', '💧', '☕', '🥤', '🥛', '🍌', '🥑', '🍞', '🐟'
+ ]
+ },
+ {
+ label: 'Schlaf & Erholung',
+ emojis: ['😴', '🌙', '🛌', '💤', '🧖', '☀️', '🌿']
+ },
+ {
+ label: 'Allgemein',
+ emojis: [
+ '🎯', '📊', '🔥', '⭐', '✨', '🎵', '📌', '🧭', '📝', '✅', '💡', '🪄'
+ ]
+ }
+]
+
+/**
+ * Wiederverwendbare Emoji-/Icon-Auswahl: Vorschau, Freitext (inkl. System-Emoji-Picker),
+ * optional ausklappbare Vorschläge.
+ *
+ * @param {string} value – aktueller Icon-String (meist ein Emoji)
+ * @param {(next: string) => void} onChange
+ * @param {string} [placeholder]
+ * @param {number} [maxLength=10]
+ * @param {boolean} [disabled]
+ * @param {string} [id] – optionale ID für das Textfeld (Label for=)
+ * @param {boolean} [defaultExpanded=false] – Vorschlags-Bereich initial offen
+ */
+export default function EmojiIconPicker({
+ value,
+ onChange,
+ placeholder = '📝',
+ maxLength = 10,
+ disabled = false,
+ id: idProp,
+ defaultExpanded = false
+}) {
+ const uid = useId()
+ const inputId = idProp || `emoji-icon-${uid}`
+ const [open, setOpen] = useState(defaultExpanded)
+
+ const handleInput = (e) => {
+ onChange(e.target.value.slice(0, maxLength))
+ }
+
+ const pick = (em) => {
+ onChange(em.slice(0, maxLength))
+ }
+
+ return (
+
+
+
+ {value || '·'}
+
+
+
+ {!!value && !disabled && (
+
+ )}
+
+ {open && (
+
+ {EMOJI_ICON_GROUPS.map((group) => (
+
+
+ {group.label}
+
+
+ {group.emojis.map((em) => (
+
+ ))}
+
+
+ ))}
+
+ Du kannst auch direkt in das Feld tippen oder das Betriebssystem-Emoji-Menü nutzen
+ (z. B. Win + . unter Windows).
+
+
+ )}
+
+ )
+}
diff --git a/frontend/src/pages/AdminFocusAreasPage.jsx b/frontend/src/pages/AdminFocusAreasPage.jsx
index 0369314..f28df7e 100644
--- a/frontend/src/pages/AdminFocusAreasPage.jsx
+++ b/frontend/src/pages/AdminFocusAreasPage.jsx
@@ -1,6 +1,7 @@
import { useState, useEffect } from 'react'
import { Plus, Pencil, Trash2, Save, X, Eye, EyeOff } from 'lucide-react'
import { api } from '../utils/api'
+import EmojiIconPicker from '../components/EmojiIconPicker'
const CATEGORIES = [
{ value: 'body_composition', label: 'Körperzusammensetzung' },
@@ -220,15 +221,18 @@ export default function AdminFocusAreasPage() {
-
@@ -332,14 +336,18 @@ export default function AdminFocusAreasPage() {
-
+
Icon
- updateField(area.id, 'icon', e.target.value)}
- style={{ width: '100%' }}
+ onChange={(icon) => updateField(area.id, 'icon', icon)}
+ placeholder="💥"
+ maxLength={10}
/>
diff --git a/frontend/src/pages/AdminGoalTypesPage.jsx b/frontend/src/pages/AdminGoalTypesPage.jsx
index 2a8855e..090cb53 100644
--- a/frontend/src/pages/AdminGoalTypesPage.jsx
+++ b/frontend/src/pages/AdminGoalTypesPage.jsx
@@ -1,6 +1,7 @@
import { useState, useEffect } from 'react'
import { Settings, Plus, Pencil, Trash2, Database } from 'lucide-react'
import { api } from '../utils/api'
+import EmojiIconPicker from '../components/EmojiIconPicker'
export default function AdminGoalTypesPage() {
const [goalTypes, setGoalTypes] = useState([])
@@ -367,14 +368,15 @@ export default function AdminGoalTypesPage() {
/>
- Icon (Emoji)
-
+ Icon (Emoji)
+
+ setFormData(f => ({ ...f, icon: e.target.value }))}
+ onChange={(icon) => setFormData((f) => ({ ...f, icon }))}
placeholder="🧘"
+ maxLength={10}
/>
diff --git a/frontend/src/pages/AdminTrainingTypesPage.jsx b/frontend/src/pages/AdminTrainingTypesPage.jsx
index 8b9ee1b..bec6d0d 100644
--- a/frontend/src/pages/AdminTrainingTypesPage.jsx
+++ b/frontend/src/pages/AdminTrainingTypesPage.jsx
@@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom'
import { Pencil, Trash2, Plus, Save, X, ArrowLeft, Settings } from 'lucide-react'
import { api } from '../utils/api'
import ProfileBuilder from '../components/ProfileBuilder'
+import EmojiIconPicker from '../components/EmojiIconPicker'
/**
* AdminTrainingTypesPage - CRUD for training types
@@ -254,13 +255,11 @@ export default function AdminTrainingTypesPage() {
Icon (Emoji)
-
setFormData({ ...formData, icon: e.target.value })}
+ onChange={(icon) => setFormData({ ...formData, icon })}
placeholder="🏃"
maxLength={10}
- style={{ width: '100%' }}
/>
@@ -495,13 +494,11 @@ export default function AdminTrainingTypesPage() {
Icon (Emoji)
-
setFormData({ ...formData, icon: e.target.value })}
+ onChange={(icon) => setFormData({ ...formData, icon })}
placeholder="🏃"
maxLength={10}
- style={{ width: '100%' }}
/>