149 lines
4.8 KiB
TypeScript
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>
|
|
);
|
|
} |