Initial commit

This commit is contained in:
Richard
2026-07-31 10:08:13 +02:00
parent 652943fb7e
commit a69f31649c
7192 changed files with 924319 additions and 0 deletions
+210
View File
@@ -0,0 +1,210 @@
import React, { useState } from 'react';
import { X, User, Award, Check, AlertCircle } from 'lucide-react';
import { apiFetch } from '../api/client';
export default function ProfileModal({ user, skills, onClose, onUserUpdated, onRefreshSkills }) {
const [displayName, setDisplayName] = useState(user?.display_name || '');
const [selectedSkillIds, setSelectedSkillIds] = useState(
user?.skills ? user.skills.map(s => s.id) : []
);
// New skill fields
const [newSkillName, setNewSkillName] = useState('');
const [newSkillDesc, setNewSkillDesc] = useState('');
const [newSkillColor, setNewSkillColor] = useState('#2563eb');
const [savingProfile, setSavingProfile] = useState(false);
const [creatingSkill, setCreatingSkill] = useState(false);
const [error, setError] = useState('');
const [success, setSuccess] = useState('');
const handleSaveProfile = async (e) => {
e.preventDefault();
setError('');
setSuccess('');
setSavingProfile(true);
try {
const updatedUser = await apiFetch('/users/me/', {
method: 'PATCH',
body: JSON.stringify({
display_name: displayName.trim(),
skill_ids: selectedSkillIds
})
});
setSuccess('Profil und Anzeigename erfolgreich aktualisiert!');
onUserUpdated(updatedUser);
} catch (err) {
setError(err.message || 'Speichern des Profils fehlgeschlagen.');
} finally {
setSavingProfile(false);
}
};
const toggleSkill = (skillId) => {
if (selectedSkillIds.includes(skillId)) {
setSelectedSkillIds(selectedSkillIds.filter(id => id !== skillId));
} else {
setSelectedSkillIds([...selectedSkillIds, skillId]);
}
};
const handleCreateSkill = async (e) => {
e.preventDefault();
if (!newSkillName.trim()) return;
setCreatingSkill(true);
setError('');
setSuccess('');
try {
await apiFetch('/skills/', {
method: 'POST',
body: JSON.stringify({
name: newSkillName.trim(),
description: newSkillDesc.trim(),
color: newSkillColor
})
});
setNewSkillName('');
setNewSkillDesc('');
setSuccess('Neue Fähigkeit erfolgreich angelegt!');
onRefreshSkills();
} catch (err) {
setError(err.message || 'Erstellen der Fähigkeit fehlgeschlagen.');
} finally {
setCreatingSkill(false);
}
};
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-950/70 backdrop-blur-sm overflow-y-auto">
<div className="bg-slate-900 w-full max-w-lg rounded-2xl border border-slate-800 p-6 shadow-xl relative my-8">
<button
onClick={onClose}
className="absolute top-4 right-4 p-2 text-slate-400 hover:text-white rounded-lg transition"
>
<X className="w-5 h-5" />
</button>
<div className="flex items-center gap-3 mb-6">
<div className="w-10 h-10 rounded-xl bg-slate-800 text-slate-200 border border-slate-700 flex items-center justify-center">
<User className="w-5 h-5" />
</div>
<div>
<h3 className="font-bold text-lg text-white">Profil & Einstellungen</h3>
<p className="text-xs text-slate-400">Anzeigenamen und Qualifikationen verwalten</p>
</div>
</div>
{error && (
<div className="mb-4 p-3 rounded-xl bg-red-500/10 border border-red-500/20 text-red-400 text-xs flex items-center gap-2">
<AlertCircle className="w-4 h-4 shrink-0" />
<span>{error}</span>
</div>
)}
{success && (
<div className="mb-4 p-3 rounded-xl bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 text-xs flex items-center gap-2">
<Check className="w-4 h-4 shrink-0" />
<span>{success}</span>
</div>
)}
<form onSubmit={handleSaveProfile} className="space-y-5">
{/* Display Name Edit Field */}
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">
Dein Anzeigename (wird in allen Schichtlisten angezeigt)
</label>
<input
type="text"
required
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
placeholder="z. B. Alex Muster"
className="w-full px-3.5 py-2.5 rounded-xl bg-slate-950 border border-slate-800 text-white text-sm focus:outline-none focus:border-slate-600"
/>
<p className="text-[11px] text-slate-500 mt-1">
Änderungen werden sofort in allen von dir gebuchten Schichten wirksam.
</p>
</div>
{/* User Skills Selection */}
<div className="space-y-2">
<label className="block text-xs font-semibold text-slate-300">
Deine Qualifikationen & Fähigkeiten
</label>
{skills.length === 0 ? (
<p className="text-xs text-slate-500 italic">Noch keine Fähigkeiten definiert.</p>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
{skills.map((s) => {
const isSelected = selectedSkillIds.includes(s.id);
return (
<div
key={s.id}
onClick={() => toggleSkill(s.id)}
className={`p-2.5 rounded-xl border cursor-pointer transition flex items-center justify-between ${
isSelected
? 'bg-slate-800 border-slate-700 text-white'
: 'bg-slate-950/60 border-slate-800/80 text-slate-400 hover:border-slate-700'
}`}
>
<div className="flex items-center gap-2">
<span className="w-2.5 h-2.5 rounded-full" style={{ backgroundColor: s.color }}></span>
<span className="text-xs font-medium">{s.name}</span>
</div>
{isSelected && <Check className="w-3.5 h-3.5 text-emerald-400" />}
</div>
);
})}
</div>
)}
</div>
<div className="pt-2 flex justify-end">
<button
type="submit"
disabled={savingProfile}
className="px-5 py-2.5 rounded-xl text-xs font-bold bg-slate-100 hover:bg-white text-slate-950 transition shadow-sm disabled:opacity-50"
>
{savingProfile ? 'Speichern...' : 'Profil speichern'}
</button>
</div>
</form>
{/* Add New Skill Section */}
<form onSubmit={handleCreateSkill} className="border-t border-slate-800 mt-6 pt-5 space-y-3">
<h4 className="font-bold text-xs text-slate-400 uppercase tracking-wider flex items-center gap-1.5">
<Award className="w-3.5 h-3.5" /> Neue Qualifikation im System anlegen
</h4>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-2">
<input
type="text"
required
placeholder="Fähigkeit (z. B. Ersthelfer)"
value={newSkillName}
onChange={(e) => setNewSkillName(e.target.value)}
className="sm:col-span-2 px-3 py-2 rounded-xl bg-slate-950 border border-slate-800 text-white text-xs"
/>
<input
type="color"
value={newSkillColor}
onChange={(e) => setNewSkillColor(e.target.value)}
className="w-full h-8 rounded-xl border-0 cursor-pointer bg-slate-950 p-1"
/>
</div>
<div className="flex justify-end">
<button
type="submit"
disabled={creatingSkill}
className="px-3.5 py-1.5 rounded-xl text-xs font-semibold bg-slate-800 hover:bg-slate-700 text-slate-200 border border-slate-700 transition"
>
{creatingSkill ? 'Anlegen...' : 'Fähigkeit hinzufügen'}
</button>
</div>
</form>
</div>
</div>
);
}