mirror of
http://88.130.71.182:3000/BlitTech/badoHair_fe.git
synced 2026-06-12 23:23:22 +00:00
144 lines
5.7 KiB
TypeScript
144 lines
5.7 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { toast } from "sonner";
|
|
import { adminGetSettings, adminUpdateSetting, StoreSetting } from "@/lib/api/settings";
|
|
import { useLanguage } from "@/contexts/LanguageContext";
|
|
import { ApiError } from "@/lib/api";
|
|
import { Save } from "lucide-react";
|
|
|
|
export default function AdminParametres() {
|
|
const { t, locale } = useLanguage();
|
|
const [settings, setSettings] = useState<StoreSetting[]>([]);
|
|
const [values, setValues] = useState<Record<string, string>>({});
|
|
const [saving, setSaving] = useState<string | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
const SETTING_META: Record<string, { label: string; description: string; type: "number" | "text" }> = {
|
|
default_booking_price: {
|
|
label: t("booking.free") === "Free" ? "Default booking price (€)" : t("booking.free") === "Kostenlos" ? "Standardpreis für Reservierungen (€)" : "Prix de réservation par défaut (€)",
|
|
description: t("booking.free") === "Free" ? "Price applied to each appointment (0 = free)" : t("booking.free") === "Kostenlos" ? "Preis pro Termin (0 = kostenlos)" : "Prix appliqué à chaque rendez-vous (0 = gratuit)",
|
|
type: "number",
|
|
},
|
|
};
|
|
|
|
useEffect(() => {
|
|
adminGetSettings()
|
|
.then((rows) => {
|
|
setSettings(rows);
|
|
const init: Record<string, string> = {};
|
|
rows.forEach((r) => {
|
|
init[r.key] = r.value !== null && r.value !== undefined ? String(r.value) : "";
|
|
});
|
|
setValues(init);
|
|
})
|
|
.catch((e) => toast.error(e instanceof ApiError ? e.message : t("admin.error")))
|
|
.finally(() => setLoading(false));
|
|
}, []);
|
|
|
|
const handleSave = async (key: string) => {
|
|
setSaving(key);
|
|
try {
|
|
const meta = SETTING_META[key];
|
|
const raw = values[key] ?? "";
|
|
const value = meta?.type === "number" ? parseFloat(raw) : raw;
|
|
await adminUpdateSetting(key, value);
|
|
toast.success(t("admin.settings.saved"));
|
|
} catch (err) {
|
|
toast.error(err instanceof ApiError ? err.message : t("admin.error"));
|
|
} finally {
|
|
setSaving(null);
|
|
}
|
|
};
|
|
|
|
const knownKeys = settings.filter((s) => SETTING_META[s.key]);
|
|
const unknownKeys = settings.filter((s) => !SETTING_META[s.key]);
|
|
|
|
return (
|
|
<div className="space-y-8 max-w-2xl">
|
|
<div>
|
|
<h2 className="font-serif text-2xl font-semibold">{t("admin.settings.title")}</h2>
|
|
<p className="text-sm text-muted-foreground mt-1">{t("admin.settings.subtitle")}</p>
|
|
</div>
|
|
|
|
{loading ? (
|
|
<div className="space-y-4">
|
|
{Array.from({ length: 3 }).map((_, i) => (
|
|
<div key={i} className="h-20 bg-muted animate-pulse rounded-lg" />
|
|
))}
|
|
</div>
|
|
) : (
|
|
<>
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">{t("admin.settings.bookings")}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
{knownKeys.map((s) => {
|
|
const meta = SETTING_META[s.key];
|
|
return (
|
|
<div key={s.key}>
|
|
<Label htmlFor={s.key}>{meta.label}</Label>
|
|
<p className="text-xs text-muted-foreground mb-2">{meta.description}</p>
|
|
<div className="flex gap-2">
|
|
<Input
|
|
id={s.key}
|
|
type={meta.type}
|
|
value={values[s.key] ?? ""}
|
|
onChange={(e) => setValues((prev) => ({ ...prev, [s.key]: e.target.value }))}
|
|
className="max-w-[200px]"
|
|
/>
|
|
<Button size="sm" onClick={() => handleSave(s.key)} disabled={saving === s.key}>
|
|
<Save className="h-4 w-4 mr-2" />
|
|
{saving === s.key ? t("admin.saving") : t("admin.save")}
|
|
</Button>
|
|
</div>
|
|
{s.updated_at && (
|
|
<p className="text-xs text-muted-foreground mt-1">
|
|
{t("admin.settings.last_updated")} {new Date(s.updated_at).toLocaleDateString(locale)}
|
|
</p>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
{knownKeys.length === 0 && (
|
|
<p className="text-sm text-muted-foreground">{t("admin.settings.no_settings")}</p>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{unknownKeys.length > 0 && (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">{t("admin.settings.other")}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{unknownKeys.map((s) => (
|
|
<div key={s.key}>
|
|
<Label htmlFor={`u-${s.key}`}>{s.key}</Label>
|
|
<div className="flex gap-2 mt-1">
|
|
<Input
|
|
id={`u-${s.key}`}
|
|
value={values[s.key] ?? ""}
|
|
onChange={(e) => setValues((prev) => ({ ...prev, [s.key]: e.target.value }))}
|
|
/>
|
|
<Button size="sm" onClick={() => handleSave(s.key)} disabled={saving === s.key}>
|
|
<Save className="h-4 w-4 mr-2" />
|
|
{saving === s.key ? t("admin.saving") : t("admin.save")}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|