Files
beauty-bookings/src/client/components/admin-treatments.tsx
elpatron 9a104e8862 Optimize: Improve table column widths and text truncation
- Added table-fixed layout for consistent column widths
- Set specific column widths: Behandlung (2/5), Kategorie (1/6), Dauer (1/12), Preis (1/12), Aktionen (1/6)
- Truncate long descriptions to 50 characters with tooltip
- Added truncate class to prevent text overflow
- Ensures all columns are always visible without horizontal scrolling
2025-10-02 01:03:28 +02:00

274 lines
9.7 KiB
TypeScript

import { useState } from "react";
import { useMutation, useQuery } from "@tanstack/react-query";
import { queryClient } from "@/client/rpc-client";
export function AdminTreatments() {
const [showForm, setShowForm] = useState(false);
const [editingTreatment, setEditingTreatment] = useState<any>(null);
const [formData, setFormData] = useState({
name: "",
description: "",
duration: 60,
price: 5000, // 50,00 € in Cent
category: "Maniküre",
});
const { data: treatments } = useQuery(
queryClient.treatments.live.list.experimental_liveOptions()
);
const { mutate: createTreatment, isPending: isCreating } = useMutation(
queryClient.treatments.create.mutationOptions()
);
const { mutate: updateTreatment, isPending: isUpdating } = useMutation(
queryClient.treatments.update.mutationOptions()
);
const { mutate: deleteTreatment } = useMutation(
queryClient.treatments.remove.mutationOptions()
);
const categories = ["Maniküre", "Pediküre", "Nageldesign", "Verlängerungen", "Sonstiges"];
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (editingTreatment) {
updateTreatment({
id: editingTreatment.id,
...formData,
}, {
onSuccess: () => {
setEditingTreatment(null);
setShowForm(false);
resetForm();
}
});
} else {
createTreatment(formData, {
onSuccess: () => {
setShowForm(false);
resetForm();
}
});
}
};
const resetForm = () => {
setFormData({
name: "",
description: "",
duration: 60,
price: 5000,
category: "Maniküre",
});
};
const handleEdit = (treatment: any) => {
setEditingTreatment(treatment);
setFormData({
name: treatment.name,
description: treatment.description,
duration: treatment.duration,
price: treatment.price,
category: treatment.category,
});
setShowForm(true);
};
const handleCancel = () => {
setShowForm(false);
setEditingTreatment(null);
resetForm();
};
return (
<div className="max-w-6xl mx-auto">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold text-gray-900">Behandlungen verwalten</h2>
<button
onClick={() => setShowForm(true)}
className="bg-pink-600 text-white px-4 py-2 rounded-md hover:bg-pink-700 font-medium"
>
Behandlung hinzufügen
</button>
</div>
{showForm && (
<div className="bg-white rounded-lg shadow-lg p-6 mb-6">
<h3 className="text-lg font-semibold mb-4">
{editingTreatment ? "Behandlung bearbeiten" : "Neue Behandlung hinzufügen"}
</h3>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Behandlungsname *
</label>
<input
type="text"
value={formData.name}
onChange={(e) => setFormData({...formData, name: e.target.value})}
className="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-pink-500 focus:border-pink-500"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Kategorie *
</label>
<select
value={formData.category}
onChange={(e) => setFormData({...formData, category: e.target.value})}
className="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-pink-500 focus:border-pink-500"
required
>
{categories.map(cat => (
<option key={cat} value={cat}>{cat}</option>
))}
</select>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Beschreibung *
</label>
<textarea
value={formData.description}
onChange={(e) => setFormData({...formData, description: e.target.value})}
rows={3}
className="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-pink-500 focus:border-pink-500"
required
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Dauer (Minuten) *
</label>
<input
type="number"
value={formData.duration}
onChange={(e) => setFormData({...formData, duration: parseInt(e.target.value)})}
min="15"
max="480"
step="15"
className="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-pink-500 focus:border-pink-500"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Preis () *
</label>
<input
type="number"
value={formData.price / 100}
onChange={(e) => setFormData({...formData, price: Math.round(parseFloat(e.target.value) * 100)})}
min="0"
step="0.01"
className="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-pink-500 focus:border-pink-500"
required
/>
</div>
</div>
<div className="flex space-x-4">
<button
type="submit"
disabled={isCreating || isUpdating}
className="bg-pink-600 text-white px-4 py-2 rounded-md hover:bg-pink-700 disabled:opacity-50 font-medium"
>
{isCreating || isUpdating ? "Speichern..." : (editingTreatment ? "Aktualisieren" : "Erstellen")}
</button>
<button
type="button"
onClick={handleCancel}
className="bg-gray-300 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-400 font-medium"
>
Abbrechen
</button>
</div>
</form>
</div>
)}
<div className="bg-white rounded-lg shadow-lg overflow-x-auto">
<table className="w-full table-fixed">
<thead className="bg-gray-50">
<tr>
<th className="w-2/5 px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Behandlung
</th>
<th className="w-1/6 px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Kategorie
</th>
<th className="w-1/12 px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Dauer
</th>
<th className="w-1/12 px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Preis
</th>
<th className="w-1/6 px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Aktionen
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{treatments?.map((treatment) => (
<tr key={treatment.id}>
<td className="px-6 py-4">
<div>
<div className="text-sm font-medium text-gray-900 truncate">{treatment.name}</div>
<div className="text-sm text-gray-500 truncate" title={treatment.description}>
{treatment.description.length > 50
? `${treatment.description.substring(0, 50)}...`
: treatment.description}
</div>
</div>
</td>
<td className="px-6 py-4 text-sm text-gray-900 truncate">
{treatment.category}
</td>
<td className="px-6 py-4 text-sm text-gray-900">
{treatment.duration} Min
</td>
<td className="px-6 py-4 text-sm text-gray-900">
{(treatment.price / 100).toFixed(2)}
</td>
<td className="px-6 py-4 text-sm font-medium space-x-2">
<button
onClick={() => handleEdit(treatment)}
className="text-pink-600 hover:text-pink-900"
>
Bearbeiten
</button>
<button
onClick={() => {
if (confirm("Bist du sicher, dass du diese Behandlung löschen möchtest?")) {
deleteTreatment(treatment.id);
}
}}
className="text-red-600 hover:text-red-900"
>
Löschen
</button>
</td>
</tr>
))}
</tbody>
</table>
{!treatments?.length && (
<div className="text-center py-8 text-gray-500">
Keine Behandlungen verfügbar. Füge deine erste Behandlung hinzu, um zu starten.
</div>
)}
</div>
</div>
);
}