ldc-groupe/resources/ts/back/Components/Inputs/SimpleVideoUpload.tsx
2026-02-25 16:15:16 +01:00

155 lines
4.7 KiB
TypeScript

import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils";
import { usePage } from "@inertiajs/react";
import { UploadIcon } from "lucide-react";
import {
ChangeEvent,
MouseEvent,
useEffect,
useRef,
useState,
} from "react";
type Props = {
width: number | string;
height: number;
className?: string;
name: string;
label: string;
value?: string;
id?: string;
required?: boolean;
disabled?: boolean;
parentErrors?: Record<string, string> | null;
setData: (name: string, value: string) => void;
};
export default function SimpleVideoUpload(props: Props) {
const {
width,
height,
className,
name,
label,
id,
required,
disabled,
parentErrors,
setData,
} = props;
const inputRef = useRef<HTMLInputElement>(null);
const { errors } = usePage<{ errors: Record<string, string> }>().props;
const localErrors = parentErrors ?? errors ?? {};
const [source, setSource] = useState<string | null>(null);
const [fileName, setFileName] = useState<string | null>(null);
const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
const url = URL.createObjectURL(file);
setSource(url);
setFileName(file.name);
// Important pour Inertia
setData(name, file as unknown as string);
};
const handleChoose = (event: React.MouseEvent<HTMLElement>) => {
event.preventDefault();
inputRef.current?.click();
};
// Nettoyage mémoire
useEffect(() => {
return () => {
if (source) {
URL.revokeObjectURL(source);
}
};
}, [source]);
return (
<div
className={cn(
"flex flex-col gap-2 w-full",
className
)}
>
<Label htmlFor={name} className="font-medium">
{label}
{required && <span className="text-destructive ml-1">*</span>}
</Label>
{/* Input caché */}
<Input
id={id}
name={name}
type="file"
accept=".mov,.mp4"
ref={inputRef}
onChange={handleFileChange}
required={required}
disabled={disabled}
className="hidden"
/>
{/* Zone preview */}
<div
className={cn(
"relative text-sm flex justify-center border border-dashed overflow-hidden whitespace-normal [&_svg]:!size-auto [&_svg]:!shrink text-muted-foreground w-full h-36",
{
"border-destructive": localErrors[name],
"cursor-pointer": !disabled,
}
)}
style={{ width }}
onClick={!disabled ? handleChoose : undefined}
>
{!source ? (
<div className="flex flex-col justify-center items-center">
<UploadIcon />
<p className="text-center break-normal w-full mt-2">
Cliquer pour sélectionner une vidéo
</p>
<small>
Format accepté : .mp4, .mov
</small>
</div>
) : (
<div className="flex flex-col gap-3 w-full items-center">
<video
className="rounded-md shadow-md max-w-full object-contain"
style={{ maxHeight: height }}
controls
src={source}
/>
<div className="text-sm text-muted-foreground text-center break-all">
{fileName}
</div>
<button
type="button"
onClick={handleChoose}
className="text-xs px-3 py-1 rounded-md bg-primary text-primary-foreground hover:opacity-90 transition"
>
Changer la vidéo
</button>
</div>
)}
</div>
{/* Erreur */}
{localErrors[name] && (
<p className="text-sm text-destructive">
{localErrors[name]}
</p>
)}
</div>
);
}