mosaicmap/components/admin/admin-section.tsx
2025-08-14 21:34:16 +08:00

149 lines
4.8 KiB
TypeScript

"use client";
import React 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";
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;
}
export function AdminSection({
section,
values,
errors,
disabled = false,
onChange,
onBlur,
className
}: 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) => {
const value = getFieldValue(field);
const error = errors[field.id];
const fieldDisabled = disabled || field.disabled;
return (
<div
key={field.id}
className={cn(
"space-y-2",
field.grid?.span && `col-span-${field.grid.span}`,
field.grid?.offset && `col-start-${field.grid.offset + 1}`
)}
>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label
htmlFor={field.id}
className={cn(
"text-sm font-medium",
field.validation?.required && "after:content-['*'] after:ml-0.5 after:text-destructive"
)}
>
{field.label}
</Label>
{field.description && (
<p className="text-xs text-muted-foreground">
{field.description}
</p>
)}
</div>
{field.type === "switch" && (
<FieldRenderer
field={field}
value={value}
error={error}
disabled={fieldDisabled}
onChange={(newValue) => onChange(field.id, newValue)}
onBlur={() => onBlur?.(field.id)}
/>
)}
</div>
{field.type !== "switch" && (
<FieldRenderer
field={field}
value={value}
error={error}
disabled={fieldDisabled}
onChange={(newValue) => onChange(field.id, newValue)}
onBlur={() => onBlur?.(field.id)}
/>
)}
</div>
);
};
// 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}
<div>
<CardTitle className="text-lg">{section.title}</CardTitle>
{section.description && (
<CardDescription className="mt-1">
{section.description}
</CardDescription>
)}
</div>
</div>
</CardHeader>
{content}
</Card>
);
}