feat(ui): add cv application frontend and configuration
This commit is contained in:
167
src/admin/sections/EducationForm.jsx
Normal file
167
src/admin/sections/EducationForm.jsx
Normal file
@@ -0,0 +1,167 @@
|
||||
import { useState } from 'react';
|
||||
import { Plus, Edit2, Trash2, X, Check } from 'lucide-react';
|
||||
|
||||
export default function EducationForm({ data, onUpdate }) {
|
||||
const [editingId, setEditingId] = useState(null);
|
||||
const [editForm, setEditForm] = useState(null);
|
||||
|
||||
const defaultForm = {
|
||||
degree: '',
|
||||
institution: '',
|
||||
period: '',
|
||||
status: ''
|
||||
};
|
||||
|
||||
const startAdd = () => {
|
||||
setEditingId('new');
|
||||
setEditForm({ ...defaultForm, id: Date.now() });
|
||||
};
|
||||
|
||||
const startEdit = (edu) => {
|
||||
setEditingId(edu.id);
|
||||
setEditForm({ ...edu });
|
||||
};
|
||||
|
||||
const cancelEdit = () => {
|
||||
setEditingId(null);
|
||||
setEditForm(null);
|
||||
};
|
||||
|
||||
const saveEdit = () => {
|
||||
if (!editForm.degree || !editForm.institution) return;
|
||||
|
||||
if (editingId === 'new') {
|
||||
onUpdate([...data, editForm]);
|
||||
} else {
|
||||
onUpdate(data.map(e => e.id === editingId ? editForm : e));
|
||||
}
|
||||
cancelEdit();
|
||||
};
|
||||
|
||||
const deleteItem = (id) => {
|
||||
onUpdate(data.filter(e => e.id !== id));
|
||||
};
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setEditForm(prev => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-xl p-6 shadow-sm border border-slate-100">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-xl font-semibold text-slate-900">Ausbildung</h2>
|
||||
{editingId !== 'new' && (
|
||||
<button
|
||||
onClick={startAdd}
|
||||
className="flex items-center gap-2 px-3 py-1.5 bg-primary-100 text-primary-700 rounded-lg hover:bg-primary-200 transition-colors text-sm"
|
||||
>
|
||||
<Plus size={16} />
|
||||
Hinzufügen
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{editingId === 'new' && editForm && (
|
||||
<div className="mb-4 p-4 bg-primary-50 rounded-lg border border-primary-200">
|
||||
<FormFields form={editForm} onChange={handleChange} onSave={saveEdit} onCancel={cancelEdit} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-3">
|
||||
{data.map((edu) => (
|
||||
<div key={edu.id}>
|
||||
{editingId === edu.id ? (
|
||||
<div className="p-4 bg-primary-50 rounded-lg border border-primary-200">
|
||||
<FormFields form={editForm} onChange={handleChange} onSave={saveEdit} onCancel={cancelEdit} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-start gap-3 p-4 bg-slate-50 rounded-lg group">
|
||||
<div className="flex-1">
|
||||
<h3 className="font-medium text-slate-900">{edu.degree}</h3>
|
||||
<p className="text-sm text-slate-600">{edu.institution} • {edu.period}</p>
|
||||
{edu.status && (
|
||||
<span className="inline-block mt-1 px-2 py-0.5 text-xs bg-amber-100 text-amber-700 rounded">
|
||||
{edu.status}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button
|
||||
onClick={() => startEdit(edu)}
|
||||
className="p-1.5 text-slate-500 hover:text-primary-600 hover:bg-primary-50 rounded"
|
||||
>
|
||||
<Edit2 size={16} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => deleteItem(edu.id)}
|
||||
className="p-1.5 text-slate-500 hover:text-red-600 hover:bg-red-50 rounded"
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FormFields({ form, onChange, onSave, onCancel }) {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<input
|
||||
type="text"
|
||||
name="degree"
|
||||
value={form.degree || ''}
|
||||
onChange={onChange}
|
||||
placeholder="Abschluss (z.B. Bachelor Informatik)"
|
||||
className="w-full px-3 py-2 rounded-lg border border-slate-200 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
/>
|
||||
<div className="grid md:grid-cols-2 gap-3">
|
||||
<input
|
||||
type="text"
|
||||
name="institution"
|
||||
value={form.institution || ''}
|
||||
onChange={onChange}
|
||||
placeholder="Institution / Universität"
|
||||
className="px-3 py-2 rounded-lg border border-slate-200 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
name="period"
|
||||
value={form.period || ''}
|
||||
onChange={onChange}
|
||||
placeholder="Zeitraum"
|
||||
className="px-3 py-2 rounded-lg border border-slate-200 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
name="status"
|
||||
value={form.status || ''}
|
||||
onChange={onChange}
|
||||
placeholder="Status (z.B. Laufend, Abgeschlossen)"
|
||||
className="w-full px-3 py-2 rounded-lg border border-slate-200 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
/>
|
||||
<div className="flex gap-2 pt-2">
|
||||
<button
|
||||
onClick={onSave}
|
||||
className="flex items-center gap-1 px-3 py-1.5 bg-primary-600 text-white rounded-lg hover:bg-primary-700 text-sm"
|
||||
>
|
||||
<Check size={16} />
|
||||
Speichern
|
||||
</button>
|
||||
<button
|
||||
onClick={onCancel}
|
||||
className="flex items-center gap-1 px-3 py-1.5 bg-slate-100 text-slate-700 rounded-lg hover:bg-slate-200 text-sm"
|
||||
>
|
||||
<X size={16} />
|
||||
Abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user