karbe/src/app/admin/rental-items/_components/ItemForm.tsx
Claude Integration 90cc7a94af
All checks were successful
CI / test (pull_request) Successful in 2m9s
fix(rental): extract category labels en fichier neutre (importable client)
2026-06-02 03:31:22 +00:00

132 lines
6.2 KiB
TypeScript

"use client";
import { useState, useTransition } from "react";
import { FormField, inputCls, selectCls, textareaCls } from "@/components/admin/FormField";
import { RENTAL_CATEGORY_LABEL, RENTAL_CATEGORIES } from "@/lib/rental-category-labels";
type Props = {
providers: { id: string; name: string; isSystemD: boolean }[];
initial?: {
providerId?: string;
category?: string;
name?: string;
description?: string | null;
imageUrl?: string | null;
pricePerDay?: string | number;
pricePerWeek?: string | number | null;
deposit?: string | number;
totalQty?: number;
withMotor?: boolean;
fuelIncluded?: boolean;
requiresLicense?: boolean;
active?: boolean;
};
action: (fd: FormData) => Promise<{ ok: false; error: string } | { ok: true } | undefined>;
submitLabel?: string;
};
export function ItemForm({ providers, initial = {}, action, submitLabel = "Enregistrer" }: Props) {
const [pending, startTransition] = useTransition();
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
function onSubmit(fd: FormData) {
setError(null);
setSuccess(null);
startTransition(async () => {
const res = await action(fd);
if (res && res.ok === false) setError(res.error);
else if (res && res.ok === true) setSuccess("Enregistré.");
});
}
return (
<form action={onSubmit} className="space-y-4">
<fieldset disabled={pending} className="space-y-4">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<FormField label="Prestataire" required>
<select name="providerId" defaultValue={initial.providerId ?? ""} required className={selectCls}>
<option value="" disabled> sélectionner </option>
{providers.map((p) => (
<option key={p.id} value={p.id}>
{p.name}{p.isSystemD ? " (System D)" : ""}
</option>
))}
</select>
</FormField>
<FormField label="Catégorie" required>
<select name="category" defaultValue={initial.category ?? ""} required className={selectCls}>
<option value="" disabled> sélectionner </option>
{RENTAL_CATEGORIES.map((c) => (
<option key={c} value={c}>{RENTAL_CATEGORY_LABEL[c]}</option>
))}
</select>
</FormField>
<FormField label="Nom de l'item" required className="sm:col-span-2">
<input name="name" defaultValue={initial.name ?? ""} required maxLength={200} className={inputCls} placeholder="ex. Hamac coton large, Pirogue 5m avec moteur 15CV" />
</FormField>
<FormField label="Description" className="sm:col-span-2">
<textarea name="description" rows={3} defaultValue={initial.description ?? ""} maxLength={5000} className={textareaCls} />
</FormField>
<FormField label="URL image" hint="Optionnel, URL publique vers photo MinIO.">
<input name="imageUrl" type="url" defaultValue={initial.imageUrl ?? ""} maxLength={500} className={inputCls} />
</FormField>
<FormField label="Stock total (qté)" required>
<input name="totalQty" type="number" min={1} max={1000} defaultValue={initial.totalQty?.toString() ?? "1"} required className={inputCls} />
</FormField>
<FormField label="Prix / jour (€)" required>
<input name="pricePerDay" type="number" min={0} step="0.5" defaultValue={initial.pricePerDay?.toString() ?? ""} required className={inputCls} />
</FormField>
<FormField label="Prix / semaine (€)" hint="Optionnel — tarif dégressif sur 7+ jours.">
<input name="pricePerWeek" type="number" min={0} step="0.5" defaultValue={initial.pricePerWeek?.toString() ?? ""} className={inputCls} />
</FormField>
<FormField label="Caution (€)" hint="Dépôt de garantie (bloqué pendant la location).">
<input name="deposit" type="number" min={0} step="1" defaultValue={initial.deposit?.toString() ?? "0"} className={inputCls} />
</FormField>
<FormField label="Statut">
<label className="flex items-center gap-2 px-1 py-2 text-sm">
<input type="checkbox" name="active" defaultChecked={initial.active ?? true} className="h-4 w-4 rounded border-zinc-300" />
Actif (visible au catalogue)
</label>
</FormField>
</div>
<fieldset className="rounded-lg border border-zinc-200 bg-zinc-50 p-3">
<legend className="px-1 text-xs font-semibold uppercase tracking-wider text-zinc-500">
Spécifications navigation
</legend>
<div className="flex flex-wrap gap-4 pt-1 text-sm">
<label className="flex items-center gap-2">
<input type="checkbox" name="withMotor" defaultChecked={initial.withMotor ?? false} className="h-4 w-4 rounded border-zinc-300" />
Avec moteur
</label>
<label className="flex items-center gap-2">
<input type="checkbox" name="fuelIncluded" defaultChecked={initial.fuelIncluded ?? false} className="h-4 w-4 rounded border-zinc-300" />
Essence incluse
</label>
<label className="flex items-center gap-2">
<input type="checkbox" name="requiresLicense" defaultChecked={initial.requiresLicense ?? false} className="h-4 w-4 rounded border-zinc-300" />
Permis bateau requis
</label>
</div>
</fieldset>
{error ? (
<div className="rounded border border-rose-200 bg-rose-50 px-3 py-2 text-sm text-rose-700">{error}</div>
) : null}
{success ? (
<div className="rounded border border-emerald-200 bg-emerald-50 px-3 py-2 text-sm text-emerald-800">{success}</div>
) : null}
<div className="flex items-center justify-end">
<button
type="submit"
className="rounded-md bg-zinc-900 px-5 py-2 text-sm font-semibold text-white hover:bg-zinc-800 disabled:opacity-50"
>
{pending ? "Enregistrement…" : submitLabel}
</button>
</div>
</fieldset>
</form>
);
}