Created
July 28, 2025 15:30
-
-
Save Klerith/913ab8e9b46768f4a56411ddcf5f6323 to your computer and use it in GitHub Desktop.
Formulario de producto
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { AdminTitle } from '@/admin/components/AdminTitle'; | |
import { Button } from '@/components/ui/button'; | |
import type { Product } from '@/interfaces/product.interface'; | |
import { X, SaveAll, Tag, Plus, Upload } from 'lucide-react'; | |
import { useState } from 'react'; | |
import { Link } from 'react-router'; | |
interface Props { | |
title: string; | |
subTitle: string; | |
product: Product; | |
} | |
const availableSizes = ['XS', 'XS', 'S', 'M', 'L', 'XL', 'XXL', 'XXL']; | |
export const ProductForm = ({ title, subTitle, product }: Props) => { | |
console.log({ product }); | |
const [dragActive, setDragActive] = useState(false); | |
const addTag = () => { | |
if (newTag.trim() && !product.tags.includes(newTag.trim())) { | |
// setProduct((prev) => ({ | |
// ...prev, | |
// tags: [...prev.tags, newTag.trim()], | |
// })); | |
} | |
}; | |
const removeTag = (tagToRemove: string) => { | |
// setProduct((prev) => ({ | |
// ...prev, | |
// tags: prev.tags.filter((tag) => tag !== tagToRemove), | |
// })); | |
}; | |
const addSize = (size: string) => { | |
// if (!product.sizes.includes(size)) { | |
// setProduct((prev) => ({ | |
// ...prev, | |
// sizes: [...prev.sizes, size], | |
// })); | |
// } | |
}; | |
const removeSize = (sizeToRemove: string) => { | |
// setProduct((prev) => ({ | |
// ...prev, | |
// sizes: prev.sizes.filter((size) => size !== sizeToRemove), | |
// })); | |
}; | |
const handleDrag = (e: React.DragEvent) => { | |
e.preventDefault(); | |
e.stopPropagation(); | |
if (e.type === 'dragenter' || e.type === 'dragover') { | |
setDragActive(true); | |
} else if (e.type === 'dragleave') { | |
setDragActive(false); | |
} | |
}; | |
const handleDrop = (e: React.DragEvent) => { | |
e.preventDefault(); | |
e.stopPropagation(); | |
setDragActive(false); | |
const files = e.dataTransfer.files; | |
console.log(files); | |
}; | |
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |
const files = e.target.files; | |
console.log(files); | |
}; | |
return ( | |
<> | |
<div className="flex justify-between items-center"> | |
<AdminTitle title={title} subtitle={subTitle} /> | |
<div className="flex justify-end mb-10 gap-4"> | |
<Button variant="outline"> | |
<Link to="/admin/products" className="flex items-center gap-2"> | |
<X className="w-4 h-4" /> | |
Cancelar | |
</Link> | |
</Button> | |
<Button> | |
<SaveAll className="w-4 h-4" /> | |
Guardar cambios | |
</Button> | |
</div> | |
</div> | |
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> | |
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8"> | |
{/* Main Form */} | |
<div className="lg:col-span-2 space-y-6"> | |
{/* Basic Information */} | |
<div className="bg-white rounded-xl shadow-lg border border-slate-200 p-6"> | |
<h2 className="text-xl font-semibold text-slate-800 mb-6"> | |
Información del producto | |
</h2> | |
<div className="space-y-6"> | |
<div> | |
<label className="block text-sm font-medium text-slate-700 mb-2"> | |
Título del producto | |
</label> | |
<input | |
type="text" | |
// value={product.title} | |
// onChange={(e) => handleInputChange('title', e.target.value)} | |
className="w-full px-4 py-3 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200" | |
placeholder="Título del producto" | |
/> | |
</div> | |
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> | |
<div> | |
<label className="block text-sm font-medium text-slate-700 mb-2"> | |
Precio ($) | |
</label> | |
<input | |
type="number" | |
value={product.price} | |
// onChange={(e) => | |
// handleInputChange('price', parseFloat(e.target.value)) | |
// } | |
className="w-full px-4 py-3 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200" | |
placeholder="Precio del producto" | |
/> | |
</div> | |
<div> | |
<label className="block text-sm font-medium text-slate-700 mb-2"> | |
Stock del producto | |
</label> | |
<input | |
type="number" | |
// value={product.stock} | |
// onChange={(e) => | |
// handleInputChange('stock', parseInt(e.target.value)) | |
// } | |
className="w-full px-4 py-3 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200" | |
placeholder="Stock del producto" | |
/> | |
</div> | |
</div> | |
<div> | |
<label className="block text-sm font-medium text-slate-700 mb-2"> | |
Slug del producto | |
</label> | |
<input | |
type="text" | |
// value={product.slug} | |
// onChange={(e) => handleInputChange('slug', e.target.value)} | |
className="w-full px-4 py-3 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200" | |
placeholder="Slug del producto" | |
/> | |
</div> | |
<div> | |
<label className="block text-sm font-medium text-slate-700 mb-2"> | |
Género del producto | |
</label> | |
<select | |
// value={product.gender} | |
// onChange={(e) => | |
// handleInputChange('gender', e.target.value) | |
// } | |
className="w-full px-4 py-3 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200" | |
> | |
<option value="men">Hombre</option> | |
<option value="women">Mujer</option> | |
<option value="unisex">Unisex</option> | |
<option value="kids">Niño</option> | |
</select> | |
</div> | |
<div> | |
<label className="block text-sm font-medium text-slate-700 mb-2"> | |
Descripción del producto | |
</label> | |
<textarea | |
// value={product.description} | |
// onChange={(e) => | |
// handleInputChange('description', e.target.value) | |
// } | |
rows={5} | |
className="w-full px-4 py-3 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 resize-none" | |
placeholder="Descripción del producto" | |
/> | |
</div> | |
</div> | |
</div> | |
{/* Sizes */} | |
<div className="bg-white rounded-xl shadow-lg border border-slate-200 p-6"> | |
<h2 className="text-xl font-semibold text-slate-800 mb-6"> | |
Tallas disponibles | |
</h2> | |
<div className="space-y-4"> | |
<div className="flex flex-wrap gap-2"> | |
{product.sizes.map((size) => ( | |
<span | |
key={size} | |
className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800 border border-blue-200" | |
> | |
{size} | |
<button | |
// onClick={() => removeSize(size)} | |
className="ml-2 text-blue-600 hover:text-blue-800 transition-colors duration-200" | |
> | |
<X className="h-3 w-3" /> | |
</button> | |
</span> | |
))} | |
</div> | |
<div className="flex flex-wrap gap-2 pt-2 border-t border-slate-200"> | |
<span className="text-sm text-slate-600 mr-2"> | |
Añadir tallas: | |
</span> | |
{availableSizes.map((size) => ( | |
<button | |
key={size} | |
// onClick={() => addSize(size)} | |
// disabled={product.sizes.includes(size)} | |
// className={`px-3 py-1 rounded-full text-sm font-medium transition-all duration-200 ${ | |
// product.sizes.includes(size) | |
// ? 'bg-slate-100 text-slate-400 cursor-not-allowed' | |
// : 'bg-slate-200 text-slate-700 hover:bg-slate-300 cursor-pointer' | |
// }`} | |
> | |
{size} | |
</button> | |
))} | |
</div> | |
</div> | |
</div> | |
{/* Tags */} | |
<div className="bg-white rounded-xl shadow-lg border border-slate-200 p-6"> | |
<h2 className="text-xl font-semibold text-slate-800 mb-6"> | |
Etiquetas | |
</h2> | |
<div className="space-y-4"> | |
<div className="flex flex-wrap gap-2"> | |
{product.tags.map((tag) => ( | |
<span | |
key={tag} | |
className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800 border border-green-200" | |
> | |
<Tag className="h-3 w-3 mr-1" /> | |
{tag} | |
<button | |
// onClick={() => removeTag(tag)} | |
className="ml-2 text-green-600 hover:text-green-800 transition-colors duration-200" | |
> | |
<X className="h-3 w-3" /> | |
</button> | |
</span> | |
))} | |
</div> | |
<div className="flex gap-2"> | |
<input | |
type="text" | |
// value={newTag} | |
// onChange={(e) => setNewTag(e.target.value)} | |
// onKeyDown={(e) => e.key === 'Enter' && addTag()} | |
placeholder="Añadir nueva etiqueta..." | |
className="flex-1 px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200" | |
/> | |
{/* TODO: */} | |
{/* <Button onClick={addTag} className="px-4 py-2rounded-lg "> | |
<Plus className="h-4 w-4" /> | |
</Button> */} | |
</div> | |
</div> | |
</div> | |
</div> | |
{/* Sidebar */} | |
<div className="space-y-6"> | |
{/* Product Images */} | |
<div className="bg-white rounded-xl shadow-lg border border-slate-200 p-6"> | |
<h2 className="text-xl font-semibold text-slate-800 mb-6"> | |
Imágenes del producto | |
</h2> | |
{/* Drag & Drop Zone */} | |
<div | |
className={`relative border-2 border-dashed rounded-lg p-6 text-center transition-all duration-200 ${ | |
dragActive | |
? 'border-blue-400 bg-blue-50' | |
: 'border-slate-300 hover:border-slate-400' | |
}`} | |
onDragEnter={handleDrag} | |
onDragLeave={handleDrag} | |
onDragOver={handleDrag} | |
onDrop={handleDrop} | |
> | |
<input | |
type="file" | |
multiple | |
accept="image/*" | |
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer" | |
onChange={handleFileChange} | |
/> | |
<div className="space-y-4"> | |
<Upload className="mx-auto h-12 w-12 text-slate-400" /> | |
<div> | |
<p className="text-lg font-medium text-slate-700"> | |
Arrastra las imágenes aquí | |
</p> | |
<p className="text-sm text-slate-500"> | |
o haz clic para buscar | |
</p> | |
</div> | |
<p className="text-xs text-slate-400"> | |
PNG, JPG, WebP hasta 10MB cada una | |
</p> | |
</div> | |
</div> | |
{/* Current Images */} | |
<div className="mt-6 space-y-3"> | |
<h3 className="text-sm font-medium text-slate-700"> | |
Imágenes actuales | |
</h3> | |
<div className="grid grid-cols-2 gap-3"> | |
{product.images.map((image, index) => ( | |
<div key={index} className="relative group"> | |
<div className="aspect-square bg-slate-100 rounded-lg border border-slate-200 flex items-center justify-center"> | |
<img | |
src={image} | |
alt="Product" | |
className="w-full h-full object-cover rounded-lg" | |
/> | |
</div> | |
<button className="absolute top-2 right-2 p-1 bg-red-500 text-white rounded-full opacity-0 group-hover:opacity-100 transition-opacity duration-200"> | |
<X className="h-3 w-3" /> | |
</button> | |
<p className="mt-1 text-xs text-slate-600 truncate"> | |
{image} | |
</p> | |
</div> | |
))} | |
</div> | |
</div> | |
</div> | |
{/* Product Status */} | |
<div className="bg-white rounded-xl shadow-lg border border-slate-200 p-6"> | |
<h2 className="text-xl font-semibold text-slate-800 mb-6"> | |
Estado del producto | |
</h2> | |
<div className="space-y-4"> | |
<div className="flex items-center justify-between p-3 bg-slate-50 rounded-lg"> | |
<span className="text-sm font-medium text-slate-700"> | |
Estado | |
</span> | |
<span className="px-2 py-1 text-xs font-medium bg-green-100 text-green-800 rounded-full"> | |
Activo | |
</span> | |
</div> | |
<div className="flex items-center justify-between p-3 bg-slate-50 rounded-lg"> | |
<span className="text-sm font-medium text-slate-700"> | |
Inventario | |
</span> | |
<span | |
className={`px-2 py-1 text-xs font-medium rounded-full ${ | |
product.stock > 5 | |
? 'bg-green-100 text-green-800' | |
: product.stock > 0 | |
? 'bg-yellow-100 text-yellow-800' | |
: 'bg-red-100 text-red-800' | |
}`} | |
> | |
{product.stock > 5 | |
? 'En stock' | |
: product.stock > 0 | |
? 'Bajo stock' | |
: 'Sin stock'} | |
</span> | |
</div> | |
<div className="flex items-center justify-between p-3 bg-slate-50 rounded-lg"> | |
<span className="text-sm font-medium text-slate-700"> | |
Imágenes | |
</span> | |
<span className="text-sm text-slate-600"> | |
{product.images.length} imágenes | |
</span> | |
</div> | |
<div className="flex items-center justify-between p-3 bg-slate-50 rounded-lg"> | |
<span className="text-sm font-medium text-slate-700"> | |
Tallas disponibles | |
</span> | |
<span className="text-sm text-slate-600"> | |
{product.sizes.length} tallas | |
</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</> | |
); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment