138 lines
4.2 KiB
TypeScript
138 lines
4.2 KiB
TypeScript
"use client";
|
|
|
|
import React, { useEffect } from "react";
|
|
import { cn } from "@/lib/utils";
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Label } from "@/components/ui/label";
|
|
|
|
import { SectionConfig, FieldConfig } from "@/types/admin-panel";
|
|
import { FieldRenderer } from "./field-renderer";
|
|
import { FormControl, FormField, FormItem, FormLabel } from "../ui/form";
|
|
import { useForm, UseFormReturn, } from "react-hook-form";
|
|
import { toast } from "sonner";
|
|
|
|
interface AdminSectionProps {
|
|
section: SectionConfig;
|
|
values?: Record<string, any>;
|
|
errors?: Record<string, string>;
|
|
disabled?: boolean;
|
|
onChange: (fieldId: string, value: any) => void;
|
|
onBlur?: (fieldId: string) => void;
|
|
className?: string;
|
|
form?: any;
|
|
}
|
|
|
|
export function AdminSection({
|
|
section,
|
|
values,
|
|
errors,
|
|
disabled = false,
|
|
onChange,
|
|
onBlur,
|
|
className,
|
|
form
|
|
}: AdminSectionProps) {
|
|
|
|
|
|
// Filter fields based on conditional rendering
|
|
const visibleFields = section.fields.filter(field => {
|
|
if (field.showWhen) {
|
|
return field.showWhen(values ?? {});
|
|
}
|
|
return true;
|
|
});
|
|
|
|
// Get field value helper
|
|
const getFieldValue = (field: FieldConfig) => {
|
|
return values?.[field.id] ?? field.value;
|
|
};
|
|
|
|
// Render field with label and description
|
|
const renderFieldWithLabel = (field: FieldConfig) => {
|
|
|
|
return (
|
|
|
|
<FormField
|
|
control={form.control}
|
|
name={field.id}
|
|
key={field.id}
|
|
render={({ field: formField }: any) => (
|
|
<FormItem
|
|
className={cn(
|
|
"space-y-2",
|
|
field.grid?.span && `col-span-${field.grid.span}`,
|
|
field.grid?.offset && `col-start-${field.grid.offset + 1}`
|
|
)}
|
|
>
|
|
<FormLabel
|
|
className={cn(
|
|
"text-sm font-medium",
|
|
field.validation?.required && "after:content-['*'] after:ml-0.5 after:text-destructive"
|
|
)}
|
|
>
|
|
{field.label}
|
|
</FormLabel>
|
|
|
|
<FormControl>
|
|
<FieldRenderer
|
|
field={field}
|
|
onChange={(newValue) => onChange(field.id, newValue)}
|
|
onBlur={() => onBlur?.(field.id)}
|
|
form_field={formField}
|
|
/>
|
|
|
|
</FormControl>
|
|
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
);
|
|
};
|
|
|
|
// Use custom render function if provided
|
|
if (section.render) {
|
|
const children = (
|
|
<div
|
|
className={cn(
|
|
"grid gap-6",
|
|
section.columns ? `grid-cols-${section.columns}` : "grid-cols-1"
|
|
)}
|
|
>
|
|
{visibleFields.map(renderFieldWithLabel)}
|
|
</div>
|
|
);
|
|
return section.render(section, children);
|
|
}
|
|
|
|
// Default card layout
|
|
const content = (
|
|
<CardContent className="space-y-6">
|
|
<div
|
|
className={cn(
|
|
"grid gap-6",
|
|
section.columns ? `grid-cols-${section.columns}` : "grid-cols-1"
|
|
)}
|
|
>
|
|
{visibleFields.map(renderFieldWithLabel)}
|
|
</div>
|
|
</CardContent>
|
|
);
|
|
|
|
return (
|
|
<Card className={className}>
|
|
<CardHeader>
|
|
<div className="flex items-center space-x-3">
|
|
{section.icon}
|
|
<CardTitle className="text-lg">{section.title}</CardTitle>
|
|
{section.description && (
|
|
<CardDescription className="mt-1">
|
|
{section.description}
|
|
</CardDescription>
|
|
)}
|
|
</div>
|
|
</CardHeader>
|
|
{content}
|
|
</Card>
|
|
);
|
|
} |