243 lines
7.5 KiB
TypeScript
243 lines
7.5 KiB
TypeScript
'use client'
|
|
|
|
import { useState } 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 { Textarea } from '@/components/ui/textarea'
|
|
import { Switch } from '@/components/ui/switch'
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from '@/components/ui/dialog'
|
|
import { Save, Upload, X, Plus, Edit } from 'lucide-react'
|
|
import { toast } from 'sonner'
|
|
import Image from 'next/image'
|
|
|
|
interface Category {
|
|
id?: string
|
|
name: string
|
|
description: string
|
|
image: string | null
|
|
isActive: boolean
|
|
}
|
|
|
|
interface CategoryFormDialogProps {
|
|
category?: Category
|
|
onSuccess?: () => void
|
|
trigger?: React.ReactNode
|
|
mode?: 'create' | 'edit'
|
|
}
|
|
|
|
export function CategoryFormDialog({
|
|
category,
|
|
onSuccess,
|
|
trigger,
|
|
mode = category ? 'edit' : 'create'
|
|
}: CategoryFormDialogProps) {
|
|
const [open, setOpen] = useState(false)
|
|
const [loading, setLoading] = useState(false)
|
|
const [formData, setFormData] = useState<Category>({
|
|
name: '',
|
|
description: '',
|
|
image: null,
|
|
isActive: true,
|
|
...category
|
|
})
|
|
|
|
const handleInputChange = (field: keyof Category, value: any) => {
|
|
setFormData(prev => ({ ...prev, [field]: value }))
|
|
}
|
|
|
|
const handleImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = e.target.files?.[0]
|
|
if (!file) return
|
|
|
|
// Mock image upload - replace with actual upload logic
|
|
const imageUrl = URL.createObjectURL(file)
|
|
setFormData(prev => ({ ...prev, image: imageUrl }))
|
|
}
|
|
|
|
const removeImage = () => {
|
|
setFormData(prev => ({ ...prev, image: null }))
|
|
}
|
|
|
|
const resetForm = () => {
|
|
setFormData({
|
|
name: '',
|
|
description: '',
|
|
image: null,
|
|
isActive: true,
|
|
...category
|
|
})
|
|
}
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
setLoading(true)
|
|
|
|
try {
|
|
const url = mode === 'edit' && category?.id
|
|
? `/api/admin/categories/${category.id}`
|
|
: '/api/admin/categories'
|
|
|
|
const method = mode === 'edit' ? 'PATCH' : 'POST'
|
|
|
|
const response = await fetch(url, {
|
|
method,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(formData)
|
|
})
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json()
|
|
throw new Error(errorData.error || 'Failed to save category')
|
|
}
|
|
|
|
toast.success(`Category ${mode === 'edit' ? 'updated' : 'created'} successfully`)
|
|
setOpen(false)
|
|
resetForm()
|
|
onSuccess?.()
|
|
} catch (error: any) {
|
|
toast.error(error.message || `Failed to ${mode === 'edit' ? 'update' : 'create'} category`)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
const defaultTrigger = mode === 'edit' ? (
|
|
<Button variant="ghost" size="sm">
|
|
<Edit className="h-4 w-4 mr-2" />
|
|
Edit
|
|
</Button>
|
|
) : (
|
|
<Button>
|
|
<Plus className="h-4 w-4 mr-2" />
|
|
Add Category
|
|
</Button>
|
|
)
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={setOpen}>
|
|
<DialogTrigger asChild>
|
|
{trigger || defaultTrigger}
|
|
</DialogTrigger>
|
|
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
|
|
<DialogHeader>
|
|
<DialogTitle>
|
|
{mode === 'edit' ? 'Edit Category' : 'Create New Category'}
|
|
</DialogTitle>
|
|
</DialogHeader>
|
|
|
|
<form onSubmit={handleSubmit} className="space-y-6">
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Category Information</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div>
|
|
<Label htmlFor="name">Category Name</Label>
|
|
<Input
|
|
id="name"
|
|
value={formData.name}
|
|
onChange={(e) => handleInputChange('name', e.target.value)}
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<Label htmlFor="description">Description</Label>
|
|
<Textarea
|
|
id="description"
|
|
value={formData.description}
|
|
onChange={(e) => handleInputChange('description', e.target.value)}
|
|
rows={4}
|
|
placeholder="Describe this category..."
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<Label htmlFor="isActive">Active Status</Label>
|
|
<Switch
|
|
id="isActive"
|
|
checked={formData.isActive}
|
|
onCheckedChange={(checked) => handleInputChange('isActive', checked)}
|
|
/>
|
|
</div>
|
|
<p className="text-sm text-gray-500">
|
|
{formData.isActive ? 'Category is visible to customers' : 'Category is hidden from customers'}
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Category Image</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="space-y-4">
|
|
<div className="flex items-center space-x-2">
|
|
<Label htmlFor="image" className="cursor-pointer">
|
|
<div className="flex items-center space-x-2 px-4 py-2 border border-dashed border-gray-300 rounded-lg hover:border-gray-400">
|
|
<Upload className="h-4 w-4" />
|
|
<span>Upload Image</span>
|
|
</div>
|
|
</Label>
|
|
<Input
|
|
id="image"
|
|
type="file"
|
|
accept="image/*"
|
|
onChange={handleImageUpload}
|
|
className="hidden"
|
|
/>
|
|
</div>
|
|
|
|
{formData.image && (
|
|
<div className="relative group">
|
|
<Image
|
|
src={formData.image}
|
|
alt="Category image"
|
|
width={200}
|
|
height={200}
|
|
className="rounded-lg object-cover w-full h-48"
|
|
/>
|
|
<Button
|
|
type="button"
|
|
variant="destructive"
|
|
size="sm"
|
|
className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity"
|
|
onClick={removeImage}
|
|
>
|
|
<X className="h-3 w-3" />
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="flex justify-end space-x-3">
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={() => setOpen(false)}
|
|
disabled={loading}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button type="submit" disabled={loading}>
|
|
<Save className="h-4 w-4 mr-2" />
|
|
{loading ? 'Saving...' : mode === 'edit' ? 'Update Category' : 'Create Category'}
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
} |