mosaicmap/components/admin/admin-section.tsx
2025-08-17 20:28:13 +08:00

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>
);
}